Switch to GMime for sametime.

Sun, 31 Mar 2019 17:45:25 -0400

author
Elliott Sales de Andrade <qulogic@pidgin.im>
date
Sun, 31 Mar 2019 17:45:25 -0400
changeset 39526
4f678f514b69
parent 39525
e1280f3aaea8
child 39527
32b6744e4dcf

Switch to GMime for sametime.

This causes a few differences (as shown in the tests):

* MIME headers are in a slightly different order,
* MIME headers use `Camel-Case` instead of `lower-case`,
* `Content-Type` includes a `charset` parameter,
* An extra newline at the end of MIME parts was dropped,
* Filenames in attachment parts are no longer quoted,
* `Content-Disposition` and image base64 data are now wrapped,
* Unix newlines are sent consistently as DOS newlines (but correctly
parsed back to Unix).

libpurple/protocols/sametime/im_mime.c file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/meson.build file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/sametime.c file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/tests/data/mime-basic.h file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/tests/data/mime-image.h file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/tests/data/mime-multiline.h file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/tests/data/mime-utf8.h file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/tests/meson.build file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/tests/test_sametime_im_mime.c file | annotate | diff | comparison | revisions
meson.build file | annotate | diff | comparison | revisions
--- a/libpurple/protocols/sametime/im_mime.c	Sat Mar 30 03:08:23 2019 -0400
+++ b/libpurple/protocols/sametime/im_mime.c	Sun Mar 31 17:45:25 2019 -0400
@@ -24,10 +24,10 @@
 #include "internal.h"
 
 #include <glib.h>
+#include <gmime/gmime.h>
 
 /* purple includes */
 #include "image-store.h"
-#include "mime.h"
 
 /* plugin includes */
 #include "sametime.h"
@@ -38,19 +38,35 @@
 static char *
 make_cid(const char *cid)
 {
-	gsize n;
-	char *c, *d;
-
 	g_return_val_if_fail(cid != NULL, NULL);
 
-	n = strlen(cid);
-	g_return_val_if_fail(n > 2, NULL);
+	return g_strdup_printf("cid:%s", cid);
+}
+
+
+/* Create a MIME parser from some input data. */
+static GMimeParser *
+create_parser(const char *data)
+{
+	GMimeStream *stream;
+	GMimeFilter *filter;
+	GMimeStream *filtered_stream;
+	GMimeParser *parser;
 
-	c = g_strndup(cid+1, n-2);
-	d = g_strdup_printf("cid:%s", c);
+	stream = g_mime_stream_mem_new_with_buffer(data, strlen(data));
+
+	filtered_stream = g_mime_stream_filter_new(stream);
+	g_object_unref(G_OBJECT(stream));
 
-	g_free(c);
-	return d;
+	/* Add a dos2unix filter so in-message newlines are simplified. */
+	filter = g_mime_filter_dos2unix_new(FALSE);
+	g_mime_stream_filter_add(GMIME_STREAM_FILTER(filtered_stream), filter);
+	g_object_unref(G_OBJECT(filter));
+
+	parser = g_mime_parser_new_with_stream(filtered_stream);
+	g_object_unref(G_OBJECT(filtered_stream));
+
+	return parser;
 }
 
 
@@ -61,8 +77,9 @@
 
 	GString *str;
 
-	PurpleMimeDocument *doc;
-	GList *parts;
+	GMimeParser *parser;
+	GMimeObject *doc;
+	int i, count;
 
 	img_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
 			g_object_unref);
@@ -70,57 +87,66 @@
 	/* don't want the contained string to ever be NULL */
 	str = g_string_new("");
 
-	doc = purple_mime_document_parse(data);
+	parser = create_parser(data);
+	doc = g_mime_parser_construct_part(parser, NULL);
+	if (!GMIME_IS_MULTIPART(doc)) {
+		g_object_unref(G_OBJECT(doc));
+		g_object_unref(G_OBJECT(parser));
+		return g_string_free(str, FALSE);
+	}
 
 	/* handle all the MIME parts */
-	parts = purple_mime_document_get_parts(doc);
-	for (; parts; parts = parts->next) {
-		PurpleMimePart *part = parts->data;
-		const char *type;
-
-		type = purple_mime_part_get_field(part, "content-type");
-		purple_debug_info("sametime", "MIME part Content-Type: %s\n",
-				type);
+	count = g_mime_multipart_get_count(GMIME_MULTIPART(doc));
+	for (i = 0; i < count; i++) {
+		GMimeObject *obj = g_mime_multipart_get_part(GMIME_MULTIPART(doc), i);
+		GMimeContentType *type = g_mime_object_get_content_type(obj);
 
 		if (!type) {
 			; /* feh */
 
-		} else if (purple_str_has_prefix(type, "image")) {
+		} else if (g_mime_content_type_is_type(type, "image", "*")) {
 			/* put images into the image store */
 
-			guchar *d_dat;
-			gsize d_len;
+			GMimePart *part = GMIME_PART(obj);
+			GMimeDataWrapper *wrapper;
+			GMimeStream *stream;
+			GByteArray *bytearray;
+			GBytes *data;
 			char *cid;
 			PurpleImage *image;
 
 			/* obtain and unencode the data */
-			purple_mime_part_get_data_decoded(part, &d_dat, &d_len);
+			bytearray = g_byte_array_new();
+			stream = g_mime_stream_mem_new_with_byte_array(bytearray);
+			g_mime_stream_mem_set_owner(GMIME_STREAM_MEM(stream), FALSE);
+			wrapper = g_mime_part_get_content(part);
+			g_mime_data_wrapper_write_to_stream(wrapper, stream);
+			data = g_byte_array_free_to_bytes(bytearray);
+			g_clear_object(&stream);
 
 			/* look up the content id */
-			cid = (char *)purple_mime_part_get_field(part,
-					"Content-ID");
-			cid = make_cid(cid);
+			cid = make_cid(g_mime_part_get_content_id(part));
 
 			/* add image to the purple image store */
-			image = purple_image_new_from_data(d_dat, d_len);
+			image = purple_image_new_from_bytes(data);
 			purple_image_set_friendly_filename(image, cid);
+			g_bytes_unref(data);
 
 			/* map the cid to the image store identifier */
 			g_hash_table_insert(img_by_cid, cid, image);
 
-		} else if (purple_str_has_prefix(type, "text")) {
-
+		} else if (GMIME_IS_TEXT_PART(obj)) {
 			/* concatenate all the text parts together */
-			guchar *data;
-			gsize len;
+			char *data;
 
-			purple_mime_part_get_data_decoded(part, &data, &len);
-			g_string_append(str, (const char *)data);
+			data = g_mime_text_part_get_text(GMIME_TEXT_PART(obj));
+			g_string_append(str, data);
 			g_free(data);
 		}
 	}
 
-	purple_mime_document_free(doc);
+	g_object_unref(G_OBJECT(doc));
+	g_object_unref(G_OBJECT(parser));
 
 	/* @todo should put this in its own function */
 	{ /* replace each IMG tag's SRC attribute with an ID attribute. This
@@ -194,37 +220,46 @@
 }
 
 
-/** generates a multipart/related content type with a random-ish
-    boundary value */
+/** generates a random-ish boundary value */
 static char *
-im_mime_content_type(void)
+im_mime_boundary(void)
 {
-	return g_strdup_printf(
-			"multipart/related; boundary=related_MW%03x_%04x",
+	return g_strdup_printf("related_MW%03x_%04x",
 			g_random_int() & 0xfff, g_random_int() & 0xffff);
 }
 
-/** determine content type from contents */
-static gchar *
-im_mime_img_content_type(PurpleImage *img)
+/** create MIME image from purple image */
+static GMimePart *
+im_mime_img_to_part(PurpleImage *img)
 {
 	const gchar *mimetype;
+	GMimePart *part;
+	GByteArray *data;
+	GMimeStream *stream;
+	GMimeDataWrapper *wrapper;
 
 	mimetype = purple_image_get_mimetype(img);
-	if (!mimetype) {
-		mimetype = "image";
+	if (mimetype && g_str_has_prefix(mimetype, "image/")) {
+		mimetype += sizeof("image/") - 1;
 	}
 
-	return g_strdup_printf("%s; name=\"%s\"", mimetype,
-			purple_image_get_friendly_filename(img));
-}
+	part = g_mime_part_new_with_type("image", mimetype);
+	g_mime_object_set_disposition(GMIME_OBJECT(part),
+			GMIME_DISPOSITION_ATTACHMENT);
+	g_mime_part_set_content_encoding(part, GMIME_CONTENT_ENCODING_BASE64);
+	g_mime_part_set_filename(part, purple_image_get_friendly_filename(img));
 
+	data = g_bytes_unref_to_array(purple_image_get_contents(img));
+	stream = g_mime_stream_mem_new_with_byte_array(data);
 
-static char *
-im_mime_img_content_disp(PurpleImage *img)
-{
-	return g_strdup_printf("attachment; filename=\"%s\"",
-		purple_image_get_friendly_filename(img));
+	wrapper = g_mime_data_wrapper_new_with_stream(stream,
+			GMIME_CONTENT_ENCODING_BINARY);
+	g_object_unref(G_OBJECT(stream));
+
+	g_mime_part_set_content(part, wrapper);
+	g_object_unref(G_OBJECT(wrapper));
+
+	return part;
 }
 
 
@@ -232,21 +267,22 @@
 im_mime_generate(const char *message)
 {
 	GString *str;
-	PurpleMimeDocument *doc;
-	PurpleMimePart *part;
+	GMimeMultipart *doc;
+	GMimeFormatOptions *opts;
+	GMimeTextPart *text;
 
 	GData *attr;
 	char *tmp, *start, *end;
 
 	str = g_string_new(NULL);
 
-	doc = purple_mime_document_new();
+	doc = g_mime_multipart_new_with_subtype("related");
 
-	purple_mime_document_set_field(doc, "Mime-Version", "1.0");
-	purple_mime_document_set_field(doc, "Content-Disposition", "inline");
+	g_mime_object_set_header(GMIME_OBJECT(doc), "Mime-Version", "1.0", NULL);
+	g_mime_object_set_disposition(GMIME_OBJECT(doc), GMIME_DISPOSITION_INLINE);
 
-	tmp = im_mime_content_type();
-	purple_mime_document_set_field(doc, "Content-Type", tmp);
+	tmp = im_mime_boundary();
+	g_mime_multipart_set_boundary(doc, tmp);
 	g_free(tmp);
 
 	tmp = (char *)message;
@@ -269,35 +305,14 @@
 		}
 
 		if (img) {
+			GMimePart *part;
 			char *cid;
-			gpointer data;
-			gsize size;
-
-			part = purple_mime_part_new(doc);
-
-			data = im_mime_img_content_disp(img);
-			purple_mime_part_set_field(part, "Content-Disposition",
-					data);
-			g_free(data);
-
-			data = im_mime_img_content_type(img);
-			purple_mime_part_set_field(part, "Content-Type", data);
-			g_free(data);
 
 			cid = im_mime_content_id();
-			data = g_strdup_printf("<%s>", cid);
-			purple_mime_part_set_field(part, "Content-ID", data);
-			g_free(data);
-
-			purple_mime_part_set_field(part,
-					"Content-transfer-encoding", "base64");
-
-			/* obtain and base64 encode the image data, and put it
-			   in the mime part */
-			size = purple_image_get_data_size(img);
-			data = g_base64_encode(purple_image_get_data(img), size);
-			purple_mime_part_set_data(part, data);
-			g_free(data);
+			part = im_mime_img_to_part(img);
+			g_mime_part_set_content_id(part, cid);
+			g_mime_multipart_add(doc, GMIME_OBJECT(part));
+			g_object_unref(G_OBJECT(part));
 
 			/* append the modified tag */
 			g_string_append_printf(str, "<img src=\"cid:%s\">", cid);
@@ -318,18 +333,22 @@
 	g_string_append(str, tmp);
 
 	/* add the text/html part */
-	part = purple_mime_part_new(doc);
-	purple_mime_part_set_field(part, "Content-Disposition", "inline");
+	text = g_mime_text_part_new_with_subtype("html");
+	g_mime_object_set_disposition(GMIME_OBJECT(text),
+			GMIME_DISPOSITION_INLINE);
 
 	tmp = purple_utf8_ncr_encode(str->str);
-	purple_mime_part_set_field(part, "Content-Type", "text/html");
-	purple_mime_part_set_field(part, "Content-Transfer-Encoding", "7bit");
-	purple_mime_part_set_data(part, tmp);
+	g_mime_text_part_set_text(text, tmp);
 	g_free(tmp);
 
+	g_mime_multipart_insert(doc, 0, GMIME_OBJECT(text));
+	g_object_unref(G_OBJECT(text));
 	g_string_free(str, TRUE);
 
-	str = g_string_new(NULL);
-	purple_mime_document_write(doc, str);
-	return g_string_free(str, FALSE);
+	opts = g_mime_format_options_new();
+	g_mime_format_options_set_newline_format(opts, GMIME_NEWLINE_FORMAT_DOS);
+	tmp = g_mime_object_to_string(GMIME_OBJECT(doc), opts);
+	g_mime_format_options_free(opts);
+	g_object_unref(G_OBJECT(doc));
+	return tmp;
 }
--- a/libpurple/protocols/sametime/meson.build	Sat Mar 30 03:08:23 2019 -0400
+++ b/libpurple/protocols/sametime/meson.build	Sun Mar 31 17:45:25 2019 -0400
@@ -8,7 +8,7 @@
 if DYNAMIC_SAMETIME
 	sametime_prpl = shared_library('sametime', SAMETIMESOURCES,
 	    c_args : ['-DG_LOG_DOMAIN="sametime"'],
-	    dependencies : [meanwhile, libpurple_dep, glib],
+	    dependencies : [meanwhile, gmime, libpurple_dep, glib],
 	    install : true, install_dir : PURPLE_PLUGINDIR)
 	
 	subdir('tests')
--- a/libpurple/protocols/sametime/sametime.c	Sat Mar 30 03:08:23 2019 -0400
+++ b/libpurple/protocols/sametime/sametime.c	Sun Mar 31 17:45:25 2019 -0400
@@ -29,6 +29,7 @@
 
 /* glib includes */
 #include <glib.h>
+#include <gmime/gmime.h>
 
 /* purple includes */
 #include "account.h"
@@ -5361,6 +5362,8 @@
   log_handler[1] = g_log_set_handler("meanwhile", logflags,
              mw_log_handler, NULL);
 
+  g_mime_init();
+
   return TRUE;
 }
 
@@ -5368,6 +5371,8 @@
 static gboolean
 plugin_unload(PurplePlugin *plugin, GError **error)
 {
+  g_mime_shutdown();
+
   g_log_remove_handler(G_LOG_DOMAIN, log_handler[0]);
   g_log_remove_handler("meanwhile", log_handler[1]);
 
--- a/libpurple/protocols/sametime/tests/data/mime-basic.h	Sat Mar 30 03:08:23 2019 -0400
+++ b/libpurple/protocols/sametime/tests/data/mime-basic.h	Sun Mar 31 17:45:25 2019 -0400
@@ -3,15 +3,14 @@
 .name = "empty",
 .html = "",
 .mime =
-	"content-disposition: inline\r\n"
-	"mime-version: 1.0\r\n"
-	"content-type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Content-Type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Mime-Version: 1.0\r\n"
+	"Content-Disposition: inline\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac\r\n"
-	"content-transfer-encoding: 7bit\r\n"
-	"content-disposition: inline\r\n"
-	"content-type: text/html\r\n"
-	"\r\n"
+	"Content-Type: text/html; charset=us-ascii\r\n"
+	"Content-Disposition: inline\r\n"
+	"Content-Transfer-Encoding: 7bit\r\n"
 	"\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac--\r\n"
@@ -21,16 +20,15 @@
 .name = "simple",
 .html = "This is a test of the MIME encoding using a short message.",
 .mime =
-	"content-disposition: inline\r\n"
-	"mime-version: 1.0\r\n"
-	"content-type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Content-Type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Mime-Version: 1.0\r\n"
+	"Content-Disposition: inline\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac\r\n"
-	"content-transfer-encoding: 7bit\r\n"
-	"content-disposition: inline\r\n"
-	"content-type: text/html\r\n"
+	"Content-Type: text/html; charset=us-ascii\r\n"
+	"Content-Disposition: inline\r\n"
+	"Content-Transfer-Encoding: 7bit\r\n"
 	"\r\n"
 	"This is a test of the MIME encoding using a short message.\r\n"
-	"\r\n"
 	"--related_MWa2f_0aac--\r\n"
 },
--- a/libpurple/protocols/sametime/tests/data/mime-image.h	Sat Mar 30 03:08:23 2019 -0400
+++ b/libpurple/protocols/sametime/tests/data/mime-image.h	Sun Mar 31 17:45:25 2019 -0400
@@ -5,30 +5,28 @@
 	"This is a test of the MIME encoding with an image: "
 	"<img src=\"" PURPLE_IMAGE_STORE_PROTOCOL "%u\">",
 .mime =
-	"content-disposition: inline\r\n"
-	"mime-version: 1.0\r\n"
-	"content-type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Content-Type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Mime-Version: 1.0\r\n"
+	"Content-Disposition: inline\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac\r\n"
-	"content-transfer-encoding: 7bit\r\n"
-	"content-disposition: inline\r\n"
-	"content-type: text/html\r\n"
+	"Content-Type: text/html; charset=us-ascii\r\n"
+	"Content-Disposition: inline\r\n"
+	"Content-Transfer-Encoding: 7bit\r\n"
 	"\r\n"
 	"This is a test of the MIME encoding with an image: "
 		"<img src=\"cid:cc0@6a675meanwhile\">\r\n"
+	"--related_MWa2f_0aac\r\n"
+	"Content-Type: image/png; "
+		"name=c6e846b12072e3bdf2c8f768e776deaa94c970ad.png\r\n"
+	"Content-Disposition: attachment;\r\n"
+	"\tfilename=c6e846b12072e3bdf2c8f768e776deaa94c970ad.png\r\n"
+	"Content-Transfer-Encoding: base64\r\n"
+	"Content-Id: <cc0@6a675meanwhile>\r\n"
 	"\r\n"
-	"--related_MWa2f_0aac\r\n"
-	"content-transfer-encoding: base64\r\n"
-	"content-disposition: attachment; "
-		"filename=\"c6e846b12072e3bdf2c8f768e776deaa94c970ad.png\"\r\n"
-	"content-id: <cc0@6a675meanwhile>\r\n"
-	"content-type: image/png; "
-		"name=\"c6e846b12072e3bdf2c8f768e776deaa94c970ad.png\"\r\n"
-	"\r\n"
-	"iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAm"
-	"pwYAAAAB3RJTUUH4AoCFjAiKKTJ3QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aX"
-	"RoIEdJTVBkLmUHAAAAFklEQVQI12P4//8/AwMD47NMtZtOCwAvqQYvitHGswAAAABJRU5"
-	"ErkJggg==\r\n"
+	"iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\r\n"
+	"B3RJTUUH4AoCFjAiKKTJ3QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH\r\n"
+	"AAAAFklEQVQI12P4//8/AwMD47NMtZtOCwAvqQYvitHGswAAAABJRU5ErkJggg==\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac--\r\n",
 .with_image = TRUE
--- a/libpurple/protocols/sametime/tests/data/mime-multiline.h	Sat Mar 30 03:08:23 2019 -0400
+++ b/libpurple/protocols/sametime/tests/data/mime-multiline.h	Sun Mar 31 17:45:25 2019 -0400
@@ -5,18 +5,17 @@
 	"This is a test of the MIME encoding using a multiline message.\n"
 	"This is the second line of the message.",
 .mime =
-	"content-disposition: inline\r\n"
-	"mime-version: 1.0\r\n"
-	"content-type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Content-Type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Mime-Version: 1.0\r\n"
+	"Content-Disposition: inline\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac\r\n"
-	"content-transfer-encoding: 7bit\r\n"
-	"content-disposition: inline\r\n"
-	"content-type: text/html\r\n"
+	"Content-Type: text/html; charset=us-ascii\r\n"
+	"Content-Disposition: inline\r\n"
+	"Content-Transfer-Encoding: 7bit\r\n"
 	"\r\n"
-	"This is a test of the MIME encoding using a multiline message.\n"
+	"This is a test of the MIME encoding using a multiline message.\r\n"
 	"This is the second line of the message.\r\n"
-	"\r\n"
 	"--related_MWa2f_0aac--\r\n"
 },
 {
@@ -24,17 +23,16 @@
 .name = "trailing-newline",
 .html = "This is a test of the MIME encoding using a multiline message.\n",
 .mime =
-	"content-disposition: inline\r\n"
-	"mime-version: 1.0\r\n"
-	"content-type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Content-Type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Mime-Version: 1.0\r\n"
+	"Content-Disposition: inline\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac\r\n"
-	"content-transfer-encoding: 7bit\r\n"
-	"content-disposition: inline\r\n"
-	"content-type: text/html\r\n"
+	"Content-Type: text/html; charset=us-ascii\r\n"
+	"Content-Disposition: inline\r\n"
+	"Content-Transfer-Encoding: 7bit\r\n"
 	"\r\n"
-	"This is a test of the MIME encoding using a multiline message.\n"
-	"\r\n"
+	"This is a test of the MIME encoding using a multiline message.\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac--\r\n"
 },
--- a/libpurple/protocols/sametime/tests/data/mime-utf8.h	Sat Mar 30 03:08:23 2019 -0400
+++ b/libpurple/protocols/sametime/tests/data/mime-utf8.h	Sun Mar 31 17:45:25 2019 -0400
@@ -3,20 +3,19 @@
 .name = "utf8-hello",
 .html = "Hello world, Καλημέρα κόσμε, コンニチハ.",
 .mime =
-	"content-disposition: inline\r\n"
-	"mime-version: 1.0\r\n"
-	"content-type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Content-Type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Mime-Version: 1.0\r\n"
+	"Content-Disposition: inline\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac\r\n"
-	"content-transfer-encoding: 7bit\r\n"
-	"content-disposition: inline\r\n"
-	"content-type: text/html\r\n"
+	"Content-Type: text/html; charset=us-ascii\r\n"
+	"Content-Disposition: inline\r\n"
+	"Content-Transfer-Encoding: 7bit\r\n"
 	"\r\n"
 	"Hello world, "
 		"&#922;&#945;&#955;&#951;&#956;&#8051;&#961;&#945; "
 		"&#954;&#8057;&#963;&#956;&#949;, "
 		"&#12467;&#12531;&#12491;&#12481;&#12495;.\r\n"
-	"\r\n"
 	"--related_MWa2f_0aac--\r\n"
 },
 {
@@ -28,14 +27,14 @@
 	"–—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд "
 	"∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა",
 .mime =
-	"content-disposition: inline\r\n"
-	"mime-version: 1.0\r\n"
-	"content-type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Content-Type: multipart/related; boundary=related_MWa2f_0aac\r\n"
+	"Mime-Version: 1.0\r\n"
+	"Content-Disposition: inline\r\n"
 	"\r\n"
 	"--related_MWa2f_0aac\r\n"
-	"content-transfer-encoding: 7bit\r\n"
-	"content-disposition: inline\r\n"
-	"content-type: text/html\r\n"
+	"Content-Type: text/html; charset=us-ascii\r\n"
+	"Content-Disposition: inline\r\n"
+	"Content-Transfer-Encoding: 7bit\r\n"
 	"\r\n"
 	"ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789 abcdefghijklmnopqrstuvwxyz "
 	"&#163;&#169;&#181;&#192;&#198;&#214;&#222;&#223;&#233;&#246;&#255; "
@@ -46,6 +45,5 @@
 	"&#8593;&#8599;&#8616;&#8635;&#8675; "
 	"&#9488;&#9532;&#9556;&#9560;&#9617;&#9658;&#9786;&#9792; "
 	"&#64257;&#65533;&#9280;&#8322;&#7968;&#7682;&#1253;&#7812;&#592;&#720;&#9038;&#1488;&#1329;&#4304;\r\n"
-	"\r\n"
 	"--related_MWa2f_0aac--\r\n"
 },
--- a/libpurple/protocols/sametime/tests/meson.build	Sat Mar 30 03:08:23 2019 -0400
+++ b/libpurple/protocols/sametime/tests/meson.build	Sun Mar 31 17:45:25 2019 -0400
@@ -2,7 +2,7 @@
 	e = executable(
 	    'test_sametime_' + prog, 'test_sametime_@0@.c'.format(prog),
 	    link_with : [sametime_prpl],
-	    dependencies : [libpurple_dep, glib])
+	    dependencies : [libpurple_dep, gmime, glib])
 
 	test('sametime_' + prog, e,
 	    env : 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()))
--- a/libpurple/protocols/sametime/tests/test_sametime_im_mime.c	Sat Mar 30 03:08:23 2019 -0400
+++ b/libpurple/protocols/sametime/tests/test_sametime_im_mime.c	Sun Mar 31 17:45:25 2019 -0400
@@ -22,6 +22,7 @@
  */
 
 #include <glib.h>
+#include <gmime/gmime.h>
 
 #include "image.h"
 #include "image-store.h"
@@ -176,6 +177,7 @@
 	gchar *name;
 
 	g_test_init(&argc, &argv, NULL);
+	g_mime_init();
 	_purple_image_store_init();
 
 	g_test_set_nonfatal_assertions();
@@ -197,5 +199,6 @@
 	i = g_test_run();
 
 	_purple_image_store_uninit();
+	g_mime_shutdown();
 	return i;
 }
--- a/meson.build	Sat Mar 30 03:08:23 2019 -0400
+++ b/meson.build	Sun Mar 31 17:45:25 2019 -0400
@@ -467,7 +467,8 @@
 #######################################################################
 if get_option('meanwhile')
 	meanwhile = dependency('meanwhile', version : ['>= 1.0.0', '< 2.0.0'], required : force_deps)
-	enable_meanwhile = meanwhile.found()
+	gmime = dependency('gmime-3.0', version : '>= 3.0.0', required : force_deps)
+	enable_meanwhile = meanwhile.found() and gmime.found()
 else
 	enable_meanwhile = false
 	meanwhile = []

mercurial