changeset 18039:c588a4a9d287

A patch from David Grohmann (dave1g) to log embedded images. Closes #725
author Richard Laager <rlaager@wiktel.com>
date Mon, 04 Jun 2007 05:47:06 +0000
parents 4cda1949878c
children 541a6b0112c6
files COPYRIGHT ChangeLog ChangeLog.API libpurple/buddyicon.c libpurple/buddyicon.h libpurple/log.c libpurple/util.c libpurple/util.h
diffstat 8 files changed, 188 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Mon Jun 04 04:52:41 2007 +0000
+++ b/COPYRIGHT	Mon Jun 04 05:47:06 2007 +0000
@@ -145,6 +145,7 @@
 Charlie Gordon
 Ryan C. Gordon
 Miah Gregory
+David Grohmann
 Christian Hammond
 Erick Hamness
 Fred Hampton
--- a/ChangeLog	Mon Jun 04 04:52:41 2007 +0000
+++ b/ChangeLog	Mon Jun 04 05:47:06 2007 +0000
@@ -9,6 +9,7 @@
 	  (Arjan van de Ven with Intel Corporation)
 	* No longer linkifies screennames containing @ signs in join/part
 	  notifications in chats
+	* With the HTML logger, images in conversations are now saved.
 
 	Pidgin:
 	* Ensure only one copy of Pidgin is running with a given configuration
--- a/ChangeLog.API	Mon Jun 04 04:52:41 2007 +0000
+++ b/ChangeLog.API	Mon Jun 04 05:47:06 2007 +0000
@@ -15,6 +15,7 @@
 	* purple_core_ensure_single_instance()
 	    This is for UIs to use to ensure only one copy is running.
 	* purple_dbus_is_owner()
+	* purple_image_data_calculate_filename()
 	* purple_timeout_add_seconds()
 	    Callers should prefer this to purple_timeout_add() for timers
 	    longer than 1 second away.  Be aware of the rounding, though.
--- a/libpurple/buddyicon.c	Mon Jun 04 04:52:41 2007 +0000
+++ b/libpurple/buddyicon.c	Mon Jun 04 05:47:06 2007 +0000
@@ -24,7 +24,6 @@
  */
 #include "internal.h"
 #include "buddyicon.h"
-#include "cipher.h"
 #include "conversation.h"
 #include "dbus-maybe.h"
 #include "debug.h"
@@ -93,33 +92,6 @@
 	}
 }
 
-static char *
-purple_buddy_icon_data_calculate_filename(guchar *icon_data, size_t icon_len)
-{
-	PurpleCipherContext *context;
-	gchar digest[41];
-
-	context = purple_cipher_context_new_by_name("sha1", NULL);
-	if (context == NULL)
-	{
-		purple_debug_error("buddyicon", "Could not find sha1 cipher\n");
-		g_return_val_if_reached(NULL);
-	}
-
-	/* Hash the icon data */
-	purple_cipher_context_append(context, icon_data, icon_len);
-	if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
-	{
-		purple_debug_error("buddyicon", "Failed to get SHA-1 digest.\n");
-		g_return_val_if_reached(NULL);
-	}
-	purple_cipher_context_destroy(context);
-
-	/* Return the filename */
-	return g_strdup_printf("%s.%s", digest,
-	                       purple_util_get_image_extension(icon_data, icon_len));
-}
-
 static void
 purple_buddy_icon_data_cache(PurpleStoredImage *img)
 {
@@ -238,7 +210,7 @@
 
 	if (filename == NULL)
 	{
-		file = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
+		file = purple_util_get_image_filename(icon_data, icon_len);
 		if (file == NULL)
 		{
 			g_free(icon_data);
@@ -966,7 +938,7 @@
 
 		g_free(path);
 
-		new_filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
+		new_filename = purple_util_get_image_filename(icon_data, icon_len);
 		if (new_filename == NULL)
 		{
 			purple_debug_error("buddyicon",
--- a/libpurple/buddyicon.h	Mon Jun 04 04:52:41 2007 +0000
+++ b/libpurple/buddyicon.h	Mon Jun 04 05:47:06 2007 +0000
@@ -31,6 +31,7 @@
 #include "blist.h"
 #include "imgstore.h"
 #include "prpl.h"
+#include "util.h"
 
 #ifdef __cplusplus
 extern "C" {
--- a/libpurple/log.c	Mon Jun 04 04:52:41 2007 +0000
+++ b/libpurple/log.c	Mon Jun 04 05:47:06 2007 +0000
@@ -32,6 +32,7 @@
 #include "prefs.h"
 #include "util.h"
 #include "stringref.h"
+#include "imgstore.h"
 
 static GSList *loggers = NULL;
 
@@ -690,6 +691,109 @@
 		return g_strdup(purple_time_format(&tm));
 }
 
+/* NOTE: This can return msg (which you may or may not want to g_free())
+ * NOTE: or a newly allocated string which you MUST g_free(). */
+static char *
+convert_image_tags(const PurpleLog *log, const char *msg)
+{
+	const char *tmp;
+	const char *start;
+	const char *end;
+	GData *attributes;
+	GString *newmsg = NULL;
+
+	tmp = msg;
+
+	newmsg = g_string_new("");
+
+	while (purple_markup_find_tag("img", tmp, &start, &end, &attributes)) {
+		int imgid = 0;
+		char *idstr = NULL;
+
+		if (newmsg == NULL)
+			newmsg = g_string_new("");
+
+		/* copy any text before the img tag */
+		if (tmp < start)
+			g_string_append_len(newmsg, tmp, start - tmp);
+
+		idstr = g_datalist_get_data(&attributes, "id");
+
+		imgid = atoi(idstr);
+		if (imgid != 0)
+		{
+			FILE *image_file;
+			char *dir;
+			PurpleStoredImage *image;
+			gconstpointer image_data;
+			char *new_filename = NULL;
+			char *path = NULL;
+			size_t image_byte_count;
+
+			image = purple_imgstore_find_by_id(imgid);
+			if (image == NULL)
+			{
+				/* This should never happen. */
+				g_string_free(newmsg, TRUE);
+				g_return_val_if_reached((char *)msg);
+			}
+
+			image_data       = purple_imgstore_get_data(image);
+			image_byte_count = purple_imgstore_get_size(image);
+			dir              = purple_log_get_log_dir(log->type, log->name, log->account);
+			new_filename     = purple_util_get_image_filename(image_data, image_byte_count);
+
+			path = g_build_filename(dir, new_filename, NULL);
+
+			/* Only save unique files. */
+			if (!g_file_test(path, G_FILE_TEST_EXISTS))
+			{
+				if ((image_file = g_fopen(path, "wb")) != NULL)
+				{
+					if (!fwrite(image_data, image_byte_count, 1, image_file))
+					{
+						purple_debug_error("log", "Error writing %s: %s\n",
+						                   path, strerror(errno));
+						fclose(image_file);
+
+						/* Attempt to not leave half-written files around. */
+						unlink(path);
+					}
+					else
+					{
+						purple_debug_info("log", "Wrote image file: %s\n", path);
+						fclose(image_file);
+					}
+				}
+				else
+				{
+					purple_debug_error("log", "Unable to create file %s: %s\n",
+					                   path, strerror(errno));
+				}
+			}
+
+			/* Write the new image tag */
+			g_string_append_printf(newmsg, "<IMG SRC=\"%s\">", new_filename);
+			g_free(new_filename);
+			g_free(path);
+		}
+
+		/* Continue from the end of the tag */
+		tmp = end + 1;
+	}
+
+	if (newmsg == NULL)
+	{
+		/* No images were found to change. */
+		return (char *)msg;
+	}
+
+	/* Append any remaining message data */
+	g_string_append(newmsg, tmp);
+
+	return g_string_free(newmsg, FALSE);
+}
+
 void purple_log_common_writer(PurpleLog *log, const char *ext)
 {
 	PurpleLogCommonLoggerData *data = log->logger_data;
@@ -1191,6 +1295,7 @@
 							  const char *from, time_t time, const char *message)
 {
 	char *msg_fixed;
+	char *image_corrected_msg;
 	char *date;
 	char *header;
 	PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
@@ -1231,7 +1336,14 @@
 	if(!data->file)
 		return 0;
 
-	purple_markup_html_to_xhtml(message, &msg_fixed, NULL);
+	image_corrected_msg = convert_image_tags(log, message);
+	purple_markup_html_to_xhtml(image_corrected_msg, &msg_fixed, NULL);
+
+	/* Yes, this breaks encapsulation.  But it's a static function and
+	 * this saves a needless strdup(). */
+	if (image_corrected_msg != message)
+		g_free(image_corrected_msg);
+
 	date = log_get_timestamp(log, time);
 
 	if(log->type == PURPLE_LOG_SYSTEM){
--- a/libpurple/util.c	Mon Jun 04 04:52:41 2007 +0000
+++ b/libpurple/util.c	Mon Jun 04 05:47:06 2007 +0000
@@ -22,6 +22,7 @@
  */
 #include "internal.h"
 
+#include "cipher.h"
 #include "conversation.h"
 #include "core.h"
 #include "debug.h"
@@ -1403,6 +1404,40 @@
 						plain = g_string_append_c(plain, '\n');
 					continue;
 				}
+				if(!g_ascii_strncasecmp(c, "<img", 4) && (*(c+4) == '>' || *(c+4) == ' ')) {
+					const char *p = c;
+					GString *src = NULL;
+					struct purple_parse_tag *pt;
+					while(*p && *p != '>') {
+						if(!g_ascii_strncasecmp(p, "src=", strlen("src="))) {
+							const char *q = p + strlen("src=");
+							src = g_string_new("");
+							if(*q == '\'' || *q == '\"')
+								q++;
+							while(*q && *q != '\"' && *q != '\'' && *q != ' ') {
+								src = g_string_append_c(src, *q);
+								q++;
+							}
+							p = q;
+						}
+						p++;
+					}
+					if ((c = strchr(c, '>')) != NULL)
+						c++;
+					else
+						c = p;
+					pt = g_new0(struct purple_parse_tag, 1);
+					pt->src_tag = "img";
+					pt->dest_tag = "img";
+					tags = g_list_prepend(tags, pt);
+					if(src && src->len)
+						g_string_append_printf(xhtml, "<img src='%s' alt=''>", g_strstrip(src->str));
+					else
+						pt->ignore = TRUE;
+					if (src)
+						g_string_free(src, TRUE);
+					continue;
+				}
 				if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>"))) {
 					struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
 					pt->src_tag = *(c+2) == '>' ? "b" : "bold";
@@ -2659,6 +2694,33 @@
 	return "icon";
 }
 
+char *
+purple_util_get_image_filename(gconstpointer image_data, size_t image_len)
+{
+	PurpleCipherContext *context;
+	gchar digest[41];
+
+	context = purple_cipher_context_new_by_name("sha1", NULL);
+	if (context == NULL)
+	{
+		purple_debug_error("util", "Could not find sha1 cipher\n");
+		g_return_val_if_reached(NULL);
+	}
+
+	/* Hash the image data */
+	purple_cipher_context_append(context, image_data, image_len);
+	if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
+	{
+		purple_debug_error("util", "Failed to get SHA-1 digest.\n");
+		g_return_val_if_reached(NULL);
+	}
+	purple_cipher_context_destroy(context);
+
+	/* Return the filename */
+	return g_strdup_printf("%s.%s", digest,
+	                       purple_util_get_image_extension(image_data, image_len));
+}
+
 gboolean
 purple_program_is_valid(const char *program)
 {
--- a/libpurple/util.h	Mon Jun 04 04:52:41 2007 +0000
+++ b/libpurple/util.h	Mon Jun 04 05:47:06 2007 +0000
@@ -32,6 +32,7 @@
 
 #include "account.h"
 #include "xmlnode.h"
+#include "notify.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -623,6 +624,12 @@
 const char *
 purple_util_get_image_extension(gconstpointer data, size_t len);
 
+/**
+ * Returns a SHA-1 hash string of the data passed in with the correct file
+ * extention appended.
+ */
+char *purple_util_get_image_filename(gconstpointer image_data, size_t image_len);
+
 /*@}*/