diff libpurple/protocols/jabber/presence.c @ 27590:a08e84032814

merge of '2348ff22f0ff3453774b8b25b36238465580c609' and 'e76f11543c2a4aa05bdf584f087cbe3439029661'
author Paul Aurich <paul@darkrain42.org>
date Sun, 12 Jul 2009 05:43:38 +0000
parents 048bcf41deef ed284238509b
children f44eda839ea4
line wrap: on
line diff
--- a/libpurple/protocols/jabber/presence.c	Sun Jul 12 05:42:40 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sun Jul 12 05:43:38 2009 +0000
@@ -59,38 +59,44 @@
 	g_free(chat_full_jid);
 }
 
-void jabber_presence_fake_to_self(JabberStream *js, const PurpleStatus *gstatus) {
-	char *my_base_jid;
+void jabber_presence_fake_to_self(JabberStream *js, PurpleStatus *status)
+{
+	PurpleAccount *account;
+	const char *username;
 
-	if(!js->user)
-		return;
+	g_return_if_fail(js->user != NULL);
 
-	my_base_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
-	if(purple_find_buddy(js->gc->account, my_base_jid)) {
-		JabberBuddy *jb;
+	account = purple_connection_get_account(js->gc);
+	username = purple_account_get_username(account);
+	if (status == NULL)
+		status = purple_account_get_active_status(account);
+
+	if (purple_find_buddy(account, username)) {
+		JabberBuddy *jb = jabber_buddy_find(js, username, TRUE);
 		JabberBuddyResource *jbr;
-		if((jb = jabber_buddy_find(js, my_base_jid, TRUE))) {
-			JabberBuddyState state;
-			char *msg;
-			int priority;
+		JabberBuddyState state;
+		char *msg;
+		int priority;
 
-			purple_status_to_jabber(gstatus, &state, &msg, &priority);
+		g_return_if_fail(jb != NULL);
+
+		purple_status_to_jabber(status, &state, &msg, &priority);
 
-			if (state == JABBER_BUDDY_STATE_UNAVAILABLE || state == JABBER_BUDDY_STATE_UNKNOWN) {
-				jabber_buddy_remove_resource(jb, js->user->resource);
-			} else {
-				jabber_buddy_track_resource(jb, js->user->resource, priority, state, msg);
-			}
-			if((jbr = jabber_buddy_find_resource(jb, NULL))) {
-				purple_prpl_got_user_status(js->gc->account, my_base_jid, jabber_buddy_state_get_status_id(jbr->state), "priority", jbr->priority, jbr->status ? "message" : NULL, jbr->status, NULL);
-			} else {
-				purple_prpl_got_user_status(js->gc->account, my_base_jid, "offline", msg ? "message" : NULL, msg, NULL);
-			}
+		if (state == JABBER_BUDDY_STATE_UNAVAILABLE ||
+				state == JABBER_BUDDY_STATE_UNKNOWN) {
+			jabber_buddy_remove_resource(jb, js->user->resource);
+		} else {
+			jabber_buddy_track_resource(jb, js->user->resource, priority,
+			                            state, msg);
+		}
 
-			g_free(msg);
+		if ((jbr = jabber_buddy_find_resource(jb, NULL))) {
+			purple_prpl_got_user_status(js->gc->account, username, jabber_buddy_state_get_status_id(jbr->state), "priority", jbr->priority, jbr->status ? "message" : NULL, jbr->status, NULL);
+		} else {
+			purple_prpl_got_user_status(js->gc->account, username, "offline", msg ? "message" : NULL, msg, NULL);
 		}
+		g_free(msg);
 	}
-	g_free(my_base_jid);
 }
 
 void jabber_set_status(PurpleAccount *account, PurpleStatus *status)
@@ -133,7 +139,7 @@
 	status = purple_presence_get_active_status(p);
 
 	/* we don't want to send presence before we've gotten our roster */
-	if(!js->roster_parsed) {
+	if (js->state != JABBER_STREAM_CONNECTED) {
 		purple_debug_info("jabber", "attempt to send presence before roster retrieved\n");
 		return;
 	}
@@ -441,6 +447,9 @@
 	jbr->caps.info = info;
 	jbr->caps.exts = exts;
 
+	if (info == NULL)
+		goto out;
+
 	if (!jbr->commands_fetched && jabber_resource_has_capability(jbr, "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");
@@ -452,6 +461,12 @@
 		jbr->commands_fetched = TRUE;
 	}
 
+	if (jabber_resource_has_capability(jbr, "http://jabber.org/protocol/chatstates"))
+		jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED;
+	else
+		jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED;
+
+out:
 	g_free(userdata->from);
 	g_free(userdata);
 }
@@ -460,9 +475,6 @@
 {
 	const char *from;
 	const char *type;
-	const char *real_jid = NULL;
-	const char *affiliation = NULL;
-	const char *role = NULL;
 	char *status = NULL;
 	int priority = 0;
 	JabberID *jid;
@@ -476,7 +488,6 @@
 	char *buddy_name;
 	JabberBuddyState state = JABBER_BUDDY_STATE_UNKNOWN;
 	xmlnode *y;
-	gboolean muc = FALSE;
 	char *avatar_hash = NULL;
 	xmlnode *caps = NULL;
 	int idle = 0;
@@ -486,34 +497,57 @@
 	from = xmlnode_get_attrib(packet, "from");
 	type = xmlnode_get_attrib(packet, "type");
 
-	if(!(jb = jabber_buddy_find(js, from, TRUE)))
-		return;
+	jb = jabber_buddy_find(js, from, TRUE);
+	g_return_if_fail(jb != NULL);
 
 	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
 			"jabber-receiving-presence", js->gc, type, from, packet));
 	if (signal_return)
 		return;
 
-	if(!(jid = jabber_id_new(from)))
+	jid = jabber_id_new(from);
+	if (jid == NULL) {
+		purple_debug_error("jabber", "Ignoring presence with malformed 'from' "
+		                   "JID: %s\n", from);
 		return;
+	}
 
 	if(jb->error_msg) {
 		g_free(jb->error_msg);
 		jb->error_msg = NULL;
 	}
 
-	if(type && !strcmp(type, "error")) {
+	if (type == NULL) {
+		xmlnode *show;
+		char *show_data = NULL;
+
+		state = JABBER_BUDDY_STATE_ONLINE;
+
+		show = xmlnode_get_child(packet, "show");
+		if (show) {
+			show_data = xmlnode_get_data(show);
+			if (show_data) {
+				state = jabber_buddy_show_get_state(show_data);
+				g_free(show_data);
+			} else
+				purple_debug_warning("jabber", "<show/> present on presence, "
+				                     "but no contents!\n");
+		}
+	} else if (g_str_equal(type, "error")) {
 		char *msg = jabber_parse_error(js, packet, NULL);
 
 		state = JABBER_BUDDY_STATE_ERROR;
 		jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence"));
-	} else if(type && !strcmp(type, "subscribe")) {
+	} else if (g_str_equal(type, "subscribe")) {
 		struct _jabber_add_permit *jap = g_new0(struct _jabber_add_permit, 1);
 		gboolean onlist = FALSE;
-		PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from);
+		PurpleAccount *account;
+		PurpleBuddy *buddy;
 		JabberBuddy *jb = NULL;
 		xmlnode *nick;
 
+		account = purple_connection_get_account(js->gc);
+		buddy = purple_find_buddy(account, from);
 		nick = xmlnode_get_child_with_namespace(packet, "nick", "http://jabber.org/protocol/nick");
 		if (nick)
 			nickname = xmlnode_get_data(nick);
@@ -528,16 +562,17 @@
 		jap->who = g_strdup(from);
 		jap->js = js;
 
-		purple_account_request_authorization(purple_connection_get_account(js->gc), from, NULL, nickname, NULL, onlist,
-				authorize_add_cb, deny_add_cb, jap);
+		purple_account_request_authorization(account, from, NULL, nickname,
+				NULL, onlist, authorize_add_cb, deny_add_cb, jap);
+
 		g_free(nickname);
 		jabber_id_free(jid);
 		return;
-	} else if(type && !strcmp(type, "subscribed")) {
+	} else if (g_str_equal(type, "subscribed")) {
 		/* we've been allowed to see their presence, but we don't care */
 		jabber_id_free(jid);
 		return;
-	} else if(type && !strcmp(type, "unsubscribe")) {
+	} else if (g_str_equal(type, "unsubscribe")) {
 		/* XXX I'm not sure this is the right way to handle this, it
 		 * might be better to add "unsubscribe" to the presence status
 		 * if lower down, but I'm not sure. */
@@ -546,14 +581,19 @@
 		 * acknowledging this (and the others) at some point. */
 		jabber_id_free(jid);
 		return;
+	} else if (g_str_equal(type, "probe")) {
+		purple_debug_warning("jabber", "Ignoring presence probe\n");
+		jabber_id_free(jid);
+		return;
+	} else if (g_str_equal(type, "unavailable")) {
+		state = JABBER_BUDDY_STATE_UNAVAILABLE;
+	} else if (g_str_equal(type, "unsubscribed")) {
+		state = JABBER_BUDDY_STATE_UNKNOWN;
 	} else {
-		if((y = xmlnode_get_child(packet, "show"))) {
-			char *show = xmlnode_get_data(y);
-			state = jabber_buddy_show_get_state(show);
-			g_free(show);
-		} else {
-			state = JABBER_BUDDY_STATE_ONLINE;
-		}
+		purple_debug_warning("jabber", "Ignoring presence with invalid type "
+		                     "'%s'\n", type);
+		jabber_id_free(jid);
+		return;
 	}
 
 
@@ -589,48 +629,6 @@
 				delayed = TRUE;
 				stamp = xmlnode_get_attrib(y, "stamp");
 			} else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user")) {
-				xmlnode *z;
-
-				muc = TRUE;
-				if((z = xmlnode_get_child(y, "status"))) {
-					const char *code = xmlnode_get_attrib(z, "code");
-					if(code && !strcmp(code, "201")) {
-						if((chat = jabber_chat_find(js, jid->node, jid->domain))) {
-							chat->config_dialog_type = PURPLE_REQUEST_ACTION;
-							chat->config_dialog_handle =
-								purple_request_action(js->gc,
-										_("Create New Room"),
-										_("Create New Room"),
-										_("You are creating a new room.  Would"
-											" you like to configure it, or"
-											" accept the default settings?"),
-										/* Default Action */ 1,
-										purple_connection_get_account(js->gc), NULL, chat->conv,
-										chat, 2,
-										_("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure),
-										_("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room));
-						}
-					} else if(code && !strcmp(code, "210")) {
-						/*  server rewrote room-nick */
-						if((chat = jabber_chat_find(js, jid->node, jid->domain))) {
-							g_free(chat->handle);
-							chat->handle = g_strdup(jid->resource);
-						}
-					}
-				}
-				if((z = xmlnode_get_child(y, "item"))) {
-					real_jid = xmlnode_get_attrib(z, "jid");
-					affiliation = xmlnode_get_attrib(z, "affiliation");
-					role = xmlnode_get_attrib(z, "role");
-					if(affiliation != NULL && !strcmp(affiliation, "owner"))
-						flags |= PURPLE_CBFLAGS_FOUNDER;
-					if (role != NULL) {
-						if (!strcmp(role, "moderator"))
-							flags |= PURPLE_CBFLAGS_OP;
-						else if (!strcmp(role, "participant"))
-							flags |= PURPLE_CBFLAGS_VOICE;
-					}
-				}
 			} else if(!strcmp(xmlns, "vcard-temp:x:update")) {
 				xmlnode *photo = xmlnode_get_child(y, "photo");
 				if(photo) {
@@ -687,75 +685,12 @@
 			return;
 		}
 
-
-		if(type && !strcmp(type, "unavailable")) {
-			gboolean nick_change = FALSE;
-
-			/* If the chat nick is invalid, we haven't yet joined, or we've
-			 * already left (it was probably us leaving after we closed the
-			 * chat), we don't care.
-			 */
-			if (!jid->resource || !chat->conv || chat->left) {
-				if (chat->left &&
-						jid->resource && chat->handle && !strcmp(jid->resource, chat->handle))
-					jabber_chat_destroy(chat);
-				jabber_id_free(jid);
-				g_free(status);
-				g_free(avatar_hash);
-				g_free(nickname);
-				return;
-			}
+		if (type == NULL) {
+			xmlnode *x;
+			const char *real_jid = NULL;
+			const char *affiliation = NULL;
+			const char *role = NULL;
 
-			jabber_buddy_remove_resource(jb, jid->resource);
-			if(chat->muc) {
-				xmlnode *x;
-				for(x = xmlnode_get_child(packet, "x"); x; x = xmlnode_get_next_twin(x)) {
-					const char *xmlns, *nick, *code;
-					xmlnode *stat, *item;
-					if(!(xmlns = xmlnode_get_namespace(x)) ||
-							strcmp(xmlns, "http://jabber.org/protocol/muc#user"))
-						continue;
-					if(!(stat = xmlnode_get_child(x, "status")))
-						continue;
-					if(!(code = xmlnode_get_attrib(stat, "code")))
-						continue;
-					if(!strcmp(code, "301")) {
-						/* XXX: we got banned */
-					} else if(!strcmp(code, "303")) {
-						if(!(item = xmlnode_get_child(x, "item")))
-							continue;
-						if(!(nick = xmlnode_get_attrib(item, "nick")))
-							continue;
-						nick_change = TRUE;
-						if(!strcmp(jid->resource, chat->handle)) {
-							g_free(chat->handle);
-							chat->handle = g_strdup(nick);
-						}
-						purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv), jid->resource, nick);
-						jabber_chat_remove_handle(chat, jid->resource);
-						break;
-					} else if(!strcmp(code, "307")) {
-						/* XXX: we got kicked */
-					} else if(!strcmp(code, "321")) {
-						/* XXX: removed due to an affiliation change */
-					} else if(!strcmp(code, "322")) {
-						/* XXX: removed because room is now members-only */
-					} else if(!strcmp(code, "332")) {
-						/* XXX: removed due to system shutdown */
-					}
-				}
-			}
-			if(!nick_change) {
-				if(!g_utf8_collate(jid->resource, chat->handle)) {
-					serv_got_chat_left(js->gc, chat->id);
-					jabber_chat_destroy(chat);
-				} else {
-					purple_conv_chat_remove_user(PURPLE_CONV_CHAT(chat->conv), jid->resource,
-							status);
-					jabber_chat_remove_handle(chat, jid->resource);
-				}
-			}
-		} else {
 			/*
 			 * XEP-0045 mandates the presence to include a resource (which is
 			 * treated as the chat nick). Some non-compliant servers allow
@@ -769,10 +704,61 @@
 				return;
 			}
 
+			x = xmlnode_get_child_with_namespace(packet, "x",
+					"http://jabber.org/protocol/muc#user");
+			if (x) {
+				xmlnode *status_node;
+				xmlnode *item_node;
+
+				status_node = xmlnode_get_child(x, "status");
+				if (status_node) {
+					const char *code = xmlnode_get_attrib(status_node, "code");
+					if (purple_strequal(code, "201")) {
+						if ((chat = jabber_chat_find(js, jid->node, jid->domain))) {
+							chat->config_dialog_type = PURPLE_REQUEST_ACTION;
+							chat->config_dialog_handle =
+								purple_request_action(js->gc,
+										_("Create New Room"),
+										_("Create New Room"),
+										_("You are creating a new room.  Would"
+											" you like to configure it, or"
+											" accept the default settings?"),
+										/* Default Action */ 1,
+										purple_connection_get_account(js->gc), NULL, chat->conv,
+										chat, 2,
+										_("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure),
+										_("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room));
+						}
+					} else if (purple_strequal(code, "210")) {
+						/* server rewrote room-nick */
+						if((chat = jabber_chat_find(js, jid->node, jid->domain))) {
+							g_free(chat->handle);
+							chat->handle = g_strdup(jid->resource);
+						}
+					}
+				}
+
+				item_node = xmlnode_get_child(x, "item");
+				if (item_node) {
+					real_jid    = xmlnode_get_attrib(item_node, "jid");
+					affiliation = xmlnode_get_attrib(item_node, "affiliation");
+					role        = xmlnode_get_attrib(item_node, "role");
+
+					if (purple_strequal(affiliation, "owner"))
+						flags |= PURPLE_CBFLAGS_FOUNDER;
+					if (role) {
+						if (g_str_equal(role, "moderator"))
+							flags |= PURPLE_CBFLAGS_OP;
+						else if (g_str_equal(role, "participant"))
+							flags |= PURPLE_CBFLAGS_VOICE;
+					}
+				}
+			}
+
 			if(!chat->conv) {
 				char *room_jid = g_strdup_printf("%s@%s", jid->node, jid->domain);
 				chat->id = i++;
-				chat->muc = muc;
+				chat->muc = (x != NULL);
 				chat->conv = serv_got_joined_chat(js->gc, chat->id, room_jid);
 				purple_conv_chat_set_nick(PURPLE_CONV_CHAT(chat->conv), chat->handle);
 
@@ -791,6 +777,153 @@
 			else
 				purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(chat->conv), jid->resource,
 						flags);
+		} else if (g_str_equal(type, "unavailable")) {
+			xmlnode *x;
+			gboolean nick_change = FALSE;
+			gboolean kick = FALSE;
+			gboolean is_our_resource = FALSE; /* Is the presence about us? */
+
+			/* If the chat nick is invalid, we haven't yet joined, or we've
+			 * already left (it was probably us leaving after we closed the
+			 * chat), we don't care.
+			 */
+			if (!jid->resource || !chat->conv || chat->left) {
+				if (chat->left &&
+						jid->resource && chat->handle && !strcmp(jid->resource, chat->handle))
+					jabber_chat_destroy(chat);
+				jabber_id_free(jid);
+				g_free(status);
+				g_free(avatar_hash);
+				g_free(nickname);
+				return;
+			}
+
+			is_our_resource = (0 == g_utf8_collate(jid->resource, chat->handle));
+
+			jabber_buddy_remove_resource(jb, jid->resource);
+
+			x = xmlnode_get_child_with_namespace(packet, "x",
+					"http://jabber.org/protocol/muc#user");
+			if (chat->muc && x) {
+				const char *nick;
+				const char *code = NULL;
+				const char *item_jid = NULL;
+				xmlnode *stat;
+				xmlnode *item;
+
+				item = xmlnode_get_child(x, "item");
+				if (item)
+					item_jid = xmlnode_get_attrib(item, "jid");
+
+
+				stat = xmlnode_get_child(x, "status");
+
+				if (stat)
+					code = xmlnode_get_attrib(stat, "code");
+
+				if (code) {
+					if(!strcmp(code, "301")) {
+						/* XXX: we got banned */
+					} else if(!strcmp(code, "303") && item &&
+							(nick = xmlnode_get_attrib(item, "nick"))) {
+						nick_change = TRUE;
+						if(!strcmp(jid->resource, chat->handle)) {
+							g_free(chat->handle);
+							chat->handle = g_strdup(nick);
+						}
+						purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv), jid->resource, nick);
+						jabber_chat_remove_handle(chat, jid->resource);
+						/* TODO: Enable this when this is in a for-loop...
+						break; */
+					} else if(!strcmp(code, "307")) {
+						/* Someone was kicked from the room */
+						xmlnode *reason = NULL, *actor = NULL;
+						const char *actor_name = NULL;
+						char *reason_text = NULL;
+						char *tmp;
+
+						kick = TRUE;
+
+						if (item) {
+							reason = xmlnode_get_child(item, "reason");
+							actor = xmlnode_get_child(item, "actor");
+
+							if (reason != NULL)
+								reason_text = xmlnode_get_data(reason);
+							if (actor != NULL)
+								actor_name = xmlnode_get_attrib(actor, "jid");
+						}
+
+						if (reason_text == NULL)
+							reason_text = g_strdup(_("No reason"));
+
+						if (is_our_resource) {
+							if (actor_name != NULL)
+								tmp = g_strdup_printf(_("You have been kicked by %s: (%s)"),
+										actor_name, reason_text);
+							else
+								tmp = g_strdup_printf(_("You have been kicked: (%s)"),
+										reason_text);
+						} else {
+							if (actor_name != NULL)
+								tmp = g_strdup_printf(_("Kicked by %s (%s)"),
+										actor_name, reason_text);
+							else
+								tmp = g_strdup_printf(_("Kicked (%s)"),
+										reason_text);
+						}
+
+						g_free(reason_text);
+						g_free(status);
+						status = tmp;
+					} else if(!strcmp(code, "321")) {
+						/* XXX: removed due to an affiliation change */
+					} else if(!strcmp(code, "322")) {
+						/* XXX: removed because room is now members-only */
+					} else if(!strcmp(code, "332")) {
+						/* XXX: removed due to system shutdown */
+					}
+				}
+
+				/*
+				 * Possibly another connected resource of our JID (see XEP-0045
+				 * v1.24 section 7.1.10) being disconnected. Should be
+				 * distinguished by the item_jid.
+				 * Also possibly works around bits of an Openfire bug. See
+				 * #8319.
+				 */
+				if (is_our_resource && !purple_strequal(from, item_jid)) {
+					/* TODO: When the above is a loop, this needs to still act
+					 * sanely for all cases (this code is a little fragile). */
+					if (!kick && !nick_change)
+						/* Presumably, kicks and nick changes also affect us. */
+						is_our_resource = FALSE;
+				}
+			}
+			if(!nick_change) {
+				if (is_our_resource) {
+					if (kick)
+						purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), jid->resource,
+								status, PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+					serv_got_chat_left(js->gc, chat->id);
+					jabber_chat_destroy(chat);
+				} else {
+					purple_conv_chat_remove_user(PURPLE_CONV_CHAT(chat->conv), jid->resource,
+							status);
+					jabber_chat_remove_handle(chat, jid->resource);
+				}
+			}
+		} else {
+			/* A type that isn't available or unavailable */
+			purple_debug_error("jabber", "MUC presence with bad type: %s\n",
+			                   type);
+
+			jabber_id_free(jid);
+			g_free(avatar_hash);
+			g_free(status);
+			g_free(nickname);
+			g_return_if_reached();
 		}
 	} else {
 		buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "",
@@ -837,8 +970,8 @@
 		}
 
 		if(state == JABBER_BUDDY_STATE_ERROR ||
-				(type && (!strcmp(type, "unavailable") ||
-						  !strcmp(type, "unsubscribed")))) {
+				(type && (g_str_equal(type, "unavailable") ||
+				          g_str_equal(type, "unsubscribed")))) {
 			PurpleConversation *conv;
 
 			jabber_buddy_remove_resource(jb, jid->resource);
@@ -867,7 +1000,7 @@
 		g_free(buddy_name);
 	}
 
-	if (caps && (!type || g_str_equal(type, "available"))) {
+	if (caps && !type) {
 		/* handle Entity Capabilities (XEP-0115) */
 		const char *node = xmlnode_get_attrib(caps, "node");
 		const char *ver  = xmlnode_get_attrib(caps, "ver");