changeset 21149:b2d9f859663e

Patch from Will Hawkins to make the SIMPLE prpl more standards compliant by keeping all subscribe/notify messaging within a dialog. This didn't apply cleanly, so I had to manually apply it - hopefully I didn't break anything during the process. Fixes #3778.
author Daniel Atallah <daniel.atallah@gmail.com>
date Tue, 06 Nov 2007 02:52:41 +0000
parents 3635ddf4170f
children 476586ae4aff
files libpurple/protocols/simple/simple.c libpurple/protocols/simple/simple.h
diffstat 2 files changed, 177 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/simple/simple.c	Mon Nov 05 22:03:58 2007 +0000
+++ b/libpurple/protocols/simple/simple.c	Tue Nov 06 02:52:41 2007 +0000
@@ -690,19 +690,15 @@
 }
 
 static char *get_contact(struct simple_account_data  *sip) {
-	return g_strdup_printf("<sip:%s@%s:%d;transport=%s>;methods=\"MESSAGE, SUBSCRIBE, NOTIFY\"", sip->username, purple_network_get_my_ip(-1), sip->listenport, sip->udp ? "udp" : "tcp");
+	return g_strdup_printf("<sip:%s@%s:%d;transport=%s>;methods=\"MESSAGE, SUBSCRIBE, NOTIFY\"",
+			       sip->username, purple_network_get_my_ip(-1),
+			       sip->listenport,
+			       sip->udp ? "udp" : "tcp");
 }
 
 static void do_register_exp(struct simple_account_data *sip, int expire) {
 	char *uri, *to, *contact, *hdr;
 
-	/* Set our default expiration to 900,
-	 * as done in the initialization of the simple_account_data
-	 * structure.
-	 */
-	if (!expire)
-		expire = 900;
-
 	sip->reregister = time(NULL) + expire - 50;
 
 	uri = g_strdup_printf("sip:%s", sip->servername);
@@ -754,11 +750,49 @@
 	purple_debug_info("simple", "got %s\n", from);
 	return from;
 }
+static gchar *find_tag(const gchar *);
 
 static gboolean process_subscribe_response(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc) {
-	gchar *to;
+	gchar *to = NULL;
+	struct simple_buddy *b = NULL;
+	gchar *theirtag = NULL, *ourtag = NULL;
+	const gchar *callid = NULL;
+
+	purple_debug_info("simple", "process subscribe response\n");
 
 	if(msg->response == 200 || msg->response == 202) {
+		if ( (to = parse_from(sipmsg_find_header(msg, "To"))) &&
+		      (b = g_hash_table_lookup(sip->buddies, to)) &&
+		       !(b->dialog))
+		{
+			purple_debug_info("simple", "creating dialog"
+				" information for a subscription.\n");
+
+			theirtag = find_tag(sipmsg_find_header(msg, "To"));
+			ourtag = find_tag(sipmsg_find_header(msg, "From"));
+			callid = sipmsg_find_header(msg, "Call-ID");
+
+			if (theirtag && ourtag && callid)
+			{
+				b->dialog = g_new0(struct sip_dialog, 1);
+				b->dialog->ourtag = g_strdup(ourtag);
+				b->dialog->theirtag = g_strdup(theirtag);
+				b->dialog->callid = g_strdup(callid);
+
+				purple_debug_info("simple", "ourtag: %s\n", 
+					ourtag);
+				purple_debug_info("simple", "theirtag: %s\n", 
+					theirtag);
+				purple_debug_info("simple", "callid: %s\n", 
+					callid);
+				g_free(theirtag);
+				g_free(ourtag);
+			}
+		}
+		else
+		{
+			purple_debug_info("simple", "cannot create dialog!\n");
+		}
 		return TRUE;
 	}
 
@@ -771,10 +805,14 @@
 	return TRUE;
 }
 
-static void simple_subscribe(struct simple_account_data *sip, struct simple_buddy *buddy) {
-	gchar *contact = "Expires: 1200\r\nAccept: application/pidf+xml, application/xpidf+xml\r\nEvent: presence\r\n";
-	gchar *to;
-	gchar *tmp;
+static void simple_subscribe_exp(struct simple_account_data *sip, struct simple_buddy *buddy, int expiration) {
+	gchar *contact, *to, *tmp, *tmp2;
+
+	tmp2 = g_strdup_printf(
+		"Expires: %d\r\n"
+		"Accept: application/pidf+xml, application/xpidf+xml\r\n"
+		"Event: presence\r\n",
+		expiration);
 
 	if(strstr(buddy->name, "sip:"))
 		to = g_strdup(buddy->name);
@@ -782,21 +820,34 @@
 		to = g_strdup_printf("sip:%s", buddy->name);
 
 	tmp = get_contact(sip);
-	contact = g_strdup_printf("%sContact: %s\r\n", contact, tmp);
+	contact = g_strdup_printf("%sContact: %s\r\n", tmp2, tmp);
 	g_free(tmp);
+	g_free(tmp2);
 
-	/* subscribe to buddy presence
-	 * we dont need to know the status so we do not need a callback */
-
-	send_sip_request(sip->gc, "SUBSCRIBE", to, to, contact, "", NULL,
-		process_subscribe_response);
+	send_sip_request(sip->gc, "SUBSCRIBE", to, to, contact,"",buddy->dialog,
+			 (expiration > 0) ? process_subscribe_response : NULL);
 
 	g_free(to);
 	g_free(contact);
 
 	/* resubscribe before subscription expires */
 	/* add some jitter */
-	buddy->resubscribe = time(NULL)+1140+(rand()%50);
+	if (expiration > 60)
+		buddy->resubscribe = time(NULL) + (expiration - 60) + (rand() % 50);
+	else if (expiration > 0)
+		buddy->resubscribe = time(NULL) + ((int) (expiration / 2));
+}
+
+static void simple_subscribe(struct simple_account_data *sip, struct simple_buddy *buddy) {
+	simple_subscribe_exp(sip, buddy, SUBSCRIBE_EXPIRATION);
+}
+
+static void simple_unsubscribe(char *name, struct simple_buddy *buddy, struct simple_account_data *sip) {
+	if (buddy->dialog)
+	{
+		purple_debug_info("simple", "Unsubscribing from %s\n", name);
+		simple_subscribe_exp(sip, buddy, 0);
+	}
 }
 
 static gboolean simple_add_lcs_contacts(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc) {
@@ -912,11 +963,20 @@
 	if(sip->reregister < curtime) {
 		do_register(sip);
 	}
+
+	/* publish status again if our last update is about to expire. */
+	if (sip->republish != -1 && 
+		sip->republish < curtime &&
+		purple_account_get_bool(sip->account, "dopublish", TRUE))
+	{
+		purple_debug_info("simple", "subscribe_timeout: republishing status.\n");
+		send_open_publish(sip);
+	}
+
 	/* check for every subscription if we need to resubscribe */
 	g_hash_table_foreach(sip->buddies, (GHFunc)simple_buddy_resub, (gpointer)sip);
 
 	/* remove a timed out suscriber */
-
 	tmp = sip->watcher;
 	while(tmp) {
 		struct simple_watcher *watcher = tmp->data;
@@ -1070,6 +1130,36 @@
 	return TRUE;
 }
 
+static gboolean dialog_match(struct sip_dialog *dialog, struct sipmsg *msg)
+{
+	const gchar *fromhdr;
+	const gchar *tohdr;
+	const gchar *callid;
+	gchar *ourtag, *theirtag;
+	gboolean match = FALSE;
+
+	fromhdr = sipmsg_find_header(msg, "From");
+	tohdr = sipmsg_find_header(msg, "To");
+	callid = sipmsg_find_header(msg, "Call-ID");
+
+	if (!fromhdr || !tohdr || !callid)
+		return FALSE;
+
+	ourtag = find_tag(tohdr);
+	theirtag = find_tag(fromhdr);
+
+	if (ourtag && theirtag &&
+			!strcmp(dialog->callid, callid) &&
+			!strcmp(dialog->ourtag, ourtag) &&
+			!strcmp(dialog->theirtag, theirtag))
+		match = TRUE;
+
+	g_free(ourtag);
+	g_free(theirtag);
+
+	return match;
+}
+
 static void process_incoming_notify(struct simple_account_data *sip, struct sipmsg *msg) {
 	gchar *from;
 	const gchar *fromhdr;
@@ -1077,16 +1167,59 @@
 	xmlnode *pidf;
 	xmlnode *basicstatus = NULL, *tuple, *status;
 	gboolean isonline = FALSE;
+	struct simple_buddy *b = NULL;
+	const gchar *sshdr = NULL;
 
 	fromhdr = sipmsg_find_header(msg, "From");
 	from = parse_from(fromhdr);
 	if(!from) return;
 
+	b = g_hash_table_lookup(sip->buddies, from);
+	if (!b)
+	{
+		g_free(from);
+		purple_debug_info("simple", "Could not find the buddy.\n");
+		return;
+	}
+
+	if (b->dialog && !dialog_match(b->dialog, msg))
+	{
+		/* We only accept notifies from people that
+		 * we already have a dialog with.
+		 */
+		purple_debug_info("simple","No corresponding dialog for notify--discard\n");
+		g_free(from);
+		return;
+	}
+
 	pidf = xmlnode_from_str(msg->body, msg->bodylen);
 
 	if(!pidf) {
 		purple_debug_info("simple", "process_incoming_notify: no parseable pidf\n");
-		purple_prpl_got_user_status(sip->account, from, "offline", NULL);
+		sshdr = sipmsg_find_header(msg, "Subscription-State");
+		if (sshdr)
+		{
+			int i = 0;
+			gchar **ssparts = g_strsplit(sshdr, ":", 0);
+			while (ssparts[i])
+			{
+				g_strchug(ssparts[i]);
+				if (g_str_has_prefix(ssparts[i], "terminated"))
+				{
+					purple_debug_info("simple", "Subscription expired!");
+					g_free(b->dialog->ourtag);
+					g_free(b->dialog->theirtag);
+					g_free(b->dialog->callid);
+					g_free(b->dialog);
+					b->dialog = NULL;
+
+					purple_prpl_got_user_status(sip->account, from, "offline", NULL);
+					break;
+				}
+				i++;
+			}
+			g_strfreev(ssparts);
+		}
 		send_sip_response(sip->gc, msg, 200, "OK", NULL);
 		g_free(from);
 		return;
@@ -1226,15 +1359,22 @@
 }
 
 static void send_open_publish(struct simple_account_data *sip) {
+	gchar *add_headers = NULL;
 	gchar *uri = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
 	gchar *doc = gen_pidf(sip, TRUE);
+
+	add_headers = g_strdup_printf("%s%d%s",
+		"Expires: ",
+		PUBLISH_EXPIRATION,
+		"\r\nEvent: presence\r\n"
+		"Content-Type: application/pidf+xml\r\n");
+
 	send_sip_request(sip->gc, "PUBLISH", uri, uri,
-		"Expires: 600\r\nEvent: presence\r\n"
-		"Content-Type: application/pidf+xml\r\n",
-		doc, NULL, process_publish_response);
-	sip->republish = time(NULL) + 500;
+		add_headers, doc, NULL, process_publish_response);
+	sip->republish = time(NULL) + PUBLISH_EXPIRATION - 50;
 	g_free(uri);
 	g_free(doc);
+	g_free(add_headers);
 }
 
 static void send_closed_publish(struct simple_account_data *sip) {
@@ -1752,11 +1892,14 @@
 		/* unregister */
 		if (sip->registerstatus == SIMPLE_REGISTER_COMPLETE)
 		{
-			if(purple_account_get_bool(sip->account, 
-				"dopublish", 
-				TRUE))
+			g_hash_table_foreach(sip->buddies,
+				(GHFunc)simple_unsubscribe,
+				(gpointer)sip);
+
+			if(purple_account_get_bool(sip->account,
+						   "dopublish", TRUE))
 				send_closed_publish(sip);
-			
+
 			do_register_exp(sip, 0);
 		}
 		connection_free_all(sip);
--- a/libpurple/protocols/simple/simple.h	Mon Nov 05 22:03:58 2007 +0000
+++ b/libpurple/protocols/simple/simple.h	Tue Nov 06 02:52:41 2007 +0000
@@ -43,6 +43,9 @@
 #define SIMPLE_REGISTER_RETRY 2
 #define SIMPLE_REGISTER_COMPLETE 3
 
+#define PUBLISH_EXPIRATION 600
+#define SUBSCRIBE_EXPIRATION 1200
+
 struct sip_dialog {
 	gchar *ourtag;
 	gchar *theirtag;
@@ -59,6 +62,7 @@
 struct simple_buddy {
 	gchar *name;
 	time_t resubscribe;
+	struct sip_dialog *dialog;
 };
 
 struct sip_auth {