changeset 13600:e1c81d199ee0

[gaim-migrate @ 15986] Rewrote oscar's incoming direct IM handler to make it more a bit less fragile if the format of the incoming IM ever changes. For example, now the binary chunks can be in a different order than the img tags committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Sun, 09 Apr 2006 21:37:12 +0000
parents 9f633bd09463
children 009a1e608419
files src/protocols/oscar/odc.c src/protocols/oscar/oscar.c
diffstat 2 files changed, 146 insertions(+), 96 deletions(-) [+]
line wrap: on
line diff
--- a/src/protocols/oscar/odc.c	Sun Apr 09 18:29:27 2006 +0000
+++ b/src/protocols/oscar/odc.c	Sun Apr 09 21:37:12 2006 +0000
@@ -204,6 +204,12 @@
 	g_free(frame.payload.data);
 }
 
+struct embedded_data
+{
+	size_t size;
+	const guint8 *data;
+};
+
 /**
  * This is called after a direct IM has been received in its entirety.  This
  * function is passed a long chunk of data which contains the IM with any
@@ -217,7 +223,7 @@
  * create the img store using the given data.
  *
  * For somewhat easy reference, here's a sample message
- * (without the whitespace and asterisks):
+ * (with added whitespace):
  *
  * <HTML><BODY BGCOLOR="#ffffff">
  *     <FONT LANG="0">
@@ -232,144 +238,189 @@
  *     <DATA ID="1" SIZE="9894">datadatadatadata</DATA>
  *     <DATA ID="2" SIZE="65978">datadatadatadata</DATA>
  * </BINARY>
- *
- * TODO: This should be rewritten to parse all the binary data first
- *       and add each image, then go through the message afterwrod and
- *       substitute in the image tags.
  */
 static void
 peer_odc_handle_payload(PeerConnection *conn, const char *msg, size_t len, int encoding, gboolean autoreply)
 {
-	OscarData *od;
 	GaimConnection *gc;
 	GaimAccount *account;
-	GaimMessageFlags imflags;
+	const char *msgend, *binary_start, *dataend;
+	const char *tmp, *start, *end, *idstr, *src, *sizestr;
+	GData *attributes;
+	GHashTable *embedded_datas;
+	struct embedded_data *embedded_data;
+	GSList *images;
 	gchar *utf8;
 	GString *newmsg;
-	GSList *images;
-	const char *msgend, *binary_start, *binary;
+	GaimMessageFlags imflags;
 
-	od = conn->od;
-	gc = od->gc;
+	gc = conn->od->gc;
 	account = gaim_connection_get_account(gc);
 
-	imflags = 0;
-	newmsg = g_string_new("");
-	images = NULL;
+	dataend = msg + len;
 
-	msgend = msg + len;
-
-	if (autoreply)
-		imflags |= GAIM_MESSAGE_AUTO_RESP;
+	/*
+	 * Create a hash table containing references to each embedded
+	 * data chunk.  The key is the "ID" and the value is an
+	 * embedded_data struct.
+	 */
+	embedded_datas = g_hash_table_new_full(g_direct_hash,
+			g_direct_equal, NULL, g_free);
 
-	/* message has a binary trailer */
-	if ((binary_start = gaim_strcasestr(msg, "<binary>")))
+	/*
+	 * Create an index of any binary chunks.  If we run into any
+	 * problems while parsing the binary data section then we stop
+	 * parsing it, and the local user will see broken image icons.
+	 */
+	/* TODO: Use a length argument when looking for the <binary> tag! */
+	binary_start = gaim_strcasestr(msg, "<binary>");
+	if (binary_start == NULL)
+		msgend = dataend;
+	else
 	{
-		GData *attribs;
-		const char *tmp, *start, *end, *last = NULL;
+		msgend = binary_start;
 
-		binary = binary_start;
-		tmp = msg;
+		/* Move our pointer to immediately after the <binary> tag */
+		tmp = binary_start + 8;
 
-		/* for each valid image tag... */
-		while (gaim_markup_find_tag("img", tmp, &start, &end, &attribs))
+		/* The embedded binary markup has a mimimum length of 29 bytes */
+		/* TODO: Use a length argument when looking for the <data> tag! */
+		while ((tmp + 29 <= dataend) &&
+				gaim_markup_find_tag("data", tmp, &start, &tmp, &attributes))
 		{
-			const char *id, *src, *datasize;
-			const char *data = NULL;
-			char *tag = NULL;
+			unsigned int id;
 			size_t size;
-			int imgid = 0;
+
+			/* Move the binary pointer from ">" to the start of the data */
+			tmp++;
 
-			/* update the location of the last img tag */
-			last = end;
+			/* Get the ID */
+			idstr = g_datalist_get_data(&attributes, "id");
+			if (idstr == NULL)
+			{
+				g_datalist_clear(&attributes);
+				break;
+			}
+			id = atoi(idstr);
 
-			/* grab attributes */
-			id       = g_datalist_get_data(&attribs, "id");
-			src      = g_datalist_get_data(&attribs, "src");
-			datasize = g_datalist_get_data(&attribs, "datasize");
-
-			/* if we have id & datasize, build the data tag */
-			if (id && datasize)
-				tag = g_strdup_printf("<data id=\"%s\" size=\"%s\">", id, datasize);
+			/* Get the size */
+			sizestr = g_datalist_get_data(&attributes, "size");
+			if (sizestr == NULL)
+			{
+				g_datalist_clear(&attributes);
+				break;
+			}
+			size = atol(sizestr);
 
-			/* if we have a tag, find the start of the data */
-			if (tag && (data = gaim_strcasestr(binary, tag)))
+			g_datalist_clear(&attributes);
+
+			if ((size > 0) && (tmp + size > dataend))
+				break;
+
+			embedded_data = g_new(struct embedded_data, 1);
+			embedded_data->size = size;
+			embedded_data->data = (const guint8 *)tmp;
+			tmp += size;
+
+			/* Skip past the closing </data> tag */
+			if (strncasecmp(tmp, "</data>", 7))
 			{
-				data += strlen(tag);
-				binary = data + atoi(datasize) + 7; /* for </data> */
+				g_free(embedded_data);
+				break;
 			}
-
-			g_free(tag);
+			tmp += 7;
 
-			/* check the data is here and store it */
-			if (data && (data + (size = atoi(datasize)) <= msgend))
-				imgid = gaim_imgstore_add(data, size, src);
+			g_hash_table_insert(embedded_datas,
+					GINT_TO_POINTER(id), embedded_data);
+		}
+	}
 
-			/* if we have a stored image... */
-			if (imgid) {
-				/* append the message up to the tag */
-				utf8 = gaim_plugin_oscar_decode_im_part(account, conn->sn,
-								encoding, 0x0000, tmp, start - tmp);
-				if (utf8 != NULL) {
-					newmsg = g_string_append(newmsg, utf8);
-					g_free(utf8);
-				}
+	/*
+	 * Loop through the message, replacing OSCAR img tags with the
+	 * equivalent Gaim img tag.
+	 */
+	images = NULL;
+	newmsg = g_string_new("");
+	tmp = msg;
+	while (gaim_markup_find_tag("img", tmp, &start, &end, &attributes))
+	{
+		int imgid = 0;
+
+		idstr   = g_datalist_get_data(&attributes, "id");
+		src     = g_datalist_get_data(&attributes, "src");
+		sizestr = g_datalist_get_data(&attributes, "datasize");
 
-				/* write the new image tag */
-				g_string_append_printf(newmsg, "<IMG ID=\"%d\">", imgid);
+		if ((idstr != NULL) && (src != NULL) && (sizestr!= NULL))
+		{
+			unsigned int id;
+			size_t size;
 
-				/* and record the image number */
+			id = atoi(idstr);
+			size = atol(sizestr);
+			embedded_data = g_hash_table_lookup(embedded_datas,
+					GINT_TO_POINTER(id));
+
+			if ((embedded_data != NULL) && (embedded_data->size == size))
+			{
+				imgid = gaim_imgstore_add(embedded_data->data, size, src);
+
+				/* Record the image number */
 				images = g_slist_append(images, GINT_TO_POINTER(imgid));
-			} else {
-				/* otherwise, copy up to the end of the tag */
-				utf8 = gaim_plugin_oscar_decode_im_part(account, conn->sn,
-								encoding, 0x0000, tmp, (end + 1) - tmp);
-				if (utf8 != NULL) {
-					newmsg = g_string_append(newmsg, utf8);
-					g_free(utf8);
-				}
 			}
-
-			/* clear the attribute list */
-			g_datalist_clear(&attribs);
-
-			/* continue from the end of the tag */
-			tmp = end + 1;
 		}
 
-		/* append any remaining message data (without the > :-)) */
-		if (last++ && (last < binary_start))
-			newmsg = g_string_append_len(newmsg, last, binary_start - last);
+		/* Delete the attribute list */
+		g_datalist_clear(&attributes);
+
+		/* Append the message up to the tag */
+		utf8 = gaim_plugin_oscar_decode_im_part(account, conn->sn,
+				encoding, 0x0000, tmp, start - tmp);
+		if (utf8 != NULL) {
+			g_string_append(newmsg, utf8);
+			g_free(utf8);
+		}
 
-		/* set the flag if we caught any images */
-		if (images)
-			imflags |= GAIM_MESSAGE_IMAGES;
-	} else {
+		if (imgid != 0)
+		{
+			/* Write the new image tag */
+			g_string_append_printf(newmsg, "<IMG ID=\"%d\">", imgid);
+		}
+
+		/* Continue from the end of the tag */
+		tmp = end + 1;
+	}
+
+	/* Append any remaining message data */
+	if (tmp <= msgend)
+	{
 		utf8 = gaim_plugin_oscar_decode_im_part(account, conn->sn,
-						encoding, 0x0000, msg, len);
+				encoding, 0x0000, tmp, msgend - tmp);
 		if (utf8 != NULL) {
 			g_string_append(newmsg, utf8);
 			g_free(utf8);
 		}
 	}
 
+	/* Send the message */
+	imflags = 0;
+	if (images != NULL)
+		imflags |= GAIM_MESSAGE_IMAGES;
+	if (autoreply)
+		imflags |= GAIM_MESSAGE_AUTO_RESP;
 	serv_got_im(gc, conn->sn, newmsg->str, imflags, time(NULL));
-
-	/* free the message */
 	g_string_free(newmsg, TRUE);
 
 	/* unref any images we allocated */
-	if (images) {
-		GSList *tmp;
-		int id;
-
-		for (tmp = images; tmp != NULL; tmp = tmp->next) {
-			id = GPOINTER_TO_INT(tmp->data);
-			gaim_imgstore_unref(id);
-		}
-
+	if (images)
+	{
+		GSList *l;
+		for (l = images; l != NULL; l = l->next)
+			gaim_imgstore_unref(GPOINTER_TO_INT(l->data));
 		g_slist_free(images);
 	}
+
+	/* Delete our list of pointers to embedded images */
+	g_hash_table_destroy(embedded_datas);
 }
 
 /**
--- a/src/protocols/oscar/oscar.c	Sun Apr 09 18:29:27 2006 +0000
+++ b/src/protocols/oscar/oscar.c	Sun Apr 09 21:37:12 2006 +0000
@@ -1841,7 +1841,6 @@
 	gchar *tmp;
 	aim_mpmsg_section_t *curpart;
 	const char *start;
-	const char *end;
 	GData *attribs;
 
 	gaim_debug_misc("oscar", "Received IM from %s with %d parts\n",
@@ -1937,7 +1936,7 @@
 	/*
 	 * Convert iChat color tags to normal font tags.
 	 */
-	if (gaim_markup_find_tag("body", tmp, &start, &end, &attribs))
+	if (gaim_markup_find_tag("body", tmp, &start, NULL, &attribs))
 	{
 		const char *ichattextcolor, *ichatballooncolor;