| 22 */ |
22 */ |
| 23 |
23 |
| 24 #include "internal.h" |
24 #include "internal.h" |
| 25 |
25 |
| 26 #include <glib.h> |
26 #include <glib.h> |
| |
27 #include <gmime/gmime.h> |
| 27 |
28 |
| 28 /* purple includes */ |
29 /* purple includes */ |
| 29 #include "image-store.h" |
30 #include "image-store.h" |
| 30 #include "mime.h" |
|
| 31 |
31 |
| 32 /* plugin includes */ |
32 /* plugin includes */ |
| 33 #include "sametime.h" |
33 #include "sametime.h" |
| 34 #include "im_mime.h" |
34 #include "im_mime.h" |
| 35 |
35 |
| 36 |
36 |
| 37 /** generate "cid:908@20582notesbuddy" from "<908@20582notesbuddy>" */ |
37 /** generate "cid:908@20582notesbuddy" from "<908@20582notesbuddy>" */ |
| 38 static char * |
38 static char * |
| 39 make_cid(const char *cid) |
39 make_cid(const char *cid) |
| 40 { |
40 { |
| 41 gsize n; |
|
| 42 char *c, *d; |
|
| 43 |
|
| 44 g_return_val_if_fail(cid != NULL, NULL); |
41 g_return_val_if_fail(cid != NULL, NULL); |
| 45 |
42 |
| 46 n = strlen(cid); |
43 return g_strdup_printf("cid:%s", cid); |
| 47 g_return_val_if_fail(n > 2, NULL); |
44 } |
| 48 |
45 |
| 49 c = g_strndup(cid+1, n-2); |
46 |
| 50 d = g_strdup_printf("cid:%s", c); |
47 /* Create a MIME parser from some input data. */ |
| 51 |
48 static GMimeParser * |
| 52 g_free(c); |
49 create_parser(const char *data) |
| 53 return d; |
50 { |
| |
51 GMimeStream *stream; |
| |
52 GMimeFilter *filter; |
| |
53 GMimeStream *filtered_stream; |
| |
54 GMimeParser *parser; |
| |
55 |
| |
56 stream = g_mime_stream_mem_new_with_buffer(data, strlen(data)); |
| |
57 |
| |
58 filtered_stream = g_mime_stream_filter_new(stream); |
| |
59 g_object_unref(G_OBJECT(stream)); |
| |
60 |
| |
61 /* Add a dos2unix filter so in-message newlines are simplified. */ |
| |
62 filter = g_mime_filter_dos2unix_new(FALSE); |
| |
63 g_mime_stream_filter_add(GMIME_STREAM_FILTER(filtered_stream), filter); |
| |
64 g_object_unref(G_OBJECT(filter)); |
| |
65 |
| |
66 parser = g_mime_parser_new_with_stream(filtered_stream); |
| |
67 g_object_unref(G_OBJECT(filtered_stream)); |
| |
68 |
| |
69 return parser; |
| 54 } |
70 } |
| 55 |
71 |
| 56 |
72 |
| 57 gchar * |
73 gchar * |
| 58 im_mime_parse(const char *data) |
74 im_mime_parse(const char *data) |
| 59 { |
75 { |
| 60 GHashTable *img_by_cid; |
76 GHashTable *img_by_cid; |
| 61 |
77 |
| 62 GString *str; |
78 GString *str; |
| 63 |
79 |
| 64 PurpleMimeDocument *doc; |
80 GMimeParser *parser; |
| 65 GList *parts; |
81 GMimeObject *doc; |
| |
82 int i, count; |
| 66 |
83 |
| 67 img_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, |
84 img_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, |
| 68 g_object_unref); |
85 g_object_unref); |
| 69 |
86 |
| 70 /* don't want the contained string to ever be NULL */ |
87 /* don't want the contained string to ever be NULL */ |
| 71 str = g_string_new(""); |
88 str = g_string_new(""); |
| 72 |
89 |
| 73 doc = purple_mime_document_parse(data); |
90 parser = create_parser(data); |
| |
91 doc = g_mime_parser_construct_part(parser, NULL); |
| |
92 if (!GMIME_IS_MULTIPART(doc)) { |
| |
93 g_object_unref(G_OBJECT(doc)); |
| |
94 g_object_unref(G_OBJECT(parser)); |
| |
95 return g_string_free(str, FALSE); |
| |
96 } |
| 74 |
97 |
| 75 /* handle all the MIME parts */ |
98 /* handle all the MIME parts */ |
| 76 parts = purple_mime_document_get_parts(doc); |
99 count = g_mime_multipart_get_count(GMIME_MULTIPART(doc)); |
| 77 for (; parts; parts = parts->next) { |
100 for (i = 0; i < count; i++) { |
| 78 PurpleMimePart *part = parts->data; |
101 GMimeObject *obj = g_mime_multipart_get_part(GMIME_MULTIPART(doc), i); |
| 79 const char *type; |
102 GMimeContentType *type = g_mime_object_get_content_type(obj); |
| 80 |
|
| 81 type = purple_mime_part_get_field(part, "content-type"); |
|
| 82 purple_debug_info("sametime", "MIME part Content-Type: %s\n", |
|
| 83 type); |
|
| 84 |
103 |
| 85 if (!type) { |
104 if (!type) { |
| 86 ; /* feh */ |
105 ; /* feh */ |
| 87 |
106 |
| 88 } else if (purple_str_has_prefix(type, "image")) { |
107 } else if (g_mime_content_type_is_type(type, "image", "*")) { |
| 89 /* put images into the image store */ |
108 /* put images into the image store */ |
| 90 |
109 |
| 91 guchar *d_dat; |
110 GMimePart *part = GMIME_PART(obj); |
| 92 gsize d_len; |
111 GMimeDataWrapper *wrapper; |
| |
112 GMimeStream *stream; |
| |
113 GByteArray *bytearray; |
| |
114 GBytes *data; |
| 93 char *cid; |
115 char *cid; |
| 94 PurpleImage *image; |
116 PurpleImage *image; |
| 95 |
117 |
| 96 /* obtain and unencode the data */ |
118 /* obtain and unencode the data */ |
| 97 purple_mime_part_get_data_decoded(part, &d_dat, &d_len); |
119 bytearray = g_byte_array_new(); |
| |
120 stream = g_mime_stream_mem_new_with_byte_array(bytearray); |
| |
121 g_mime_stream_mem_set_owner(GMIME_STREAM_MEM(stream), FALSE); |
| |
122 wrapper = g_mime_part_get_content(part); |
| |
123 g_mime_data_wrapper_write_to_stream(wrapper, stream); |
| |
124 data = g_byte_array_free_to_bytes(bytearray); |
| |
125 g_clear_object(&stream); |
| 98 |
126 |
| 99 /* look up the content id */ |
127 /* look up the content id */ |
| 100 cid = (char *)purple_mime_part_get_field(part, |
128 cid = make_cid(g_mime_part_get_content_id(part)); |
| 101 "Content-ID"); |
|
| 102 cid = make_cid(cid); |
|
| 103 |
129 |
| 104 /* add image to the purple image store */ |
130 /* add image to the purple image store */ |
| 105 image = purple_image_new_from_data(d_dat, d_len); |
131 image = purple_image_new_from_bytes(data); |
| 106 purple_image_set_friendly_filename(image, cid); |
132 purple_image_set_friendly_filename(image, cid); |
| |
133 g_bytes_unref(data); |
| 107 |
134 |
| 108 /* map the cid to the image store identifier */ |
135 /* map the cid to the image store identifier */ |
| 109 g_hash_table_insert(img_by_cid, cid, image); |
136 g_hash_table_insert(img_by_cid, cid, image); |
| 110 |
137 |
| 111 } else if (purple_str_has_prefix(type, "text")) { |
138 } else if (GMIME_IS_TEXT_PART(obj)) { |
| 112 |
|
| 113 /* concatenate all the text parts together */ |
139 /* concatenate all the text parts together */ |
| 114 guchar *data; |
140 char *data; |
| 115 gsize len; |
141 |
| 116 |
142 data = g_mime_text_part_get_text(GMIME_TEXT_PART(obj)); |
| 117 purple_mime_part_get_data_decoded(part, &data, &len); |
143 g_string_append(str, data); |
| 118 g_string_append(str, (const char *)data); |
|
| 119 g_free(data); |
144 g_free(data); |
| 120 } |
145 } |
| 121 } |
146 } |
| 122 |
147 |
| 123 purple_mime_document_free(doc); |
148 g_object_unref(G_OBJECT(doc)); |
| |
149 g_object_unref(G_OBJECT(parser)); |
| 124 |
150 |
| 125 /* @todo should put this in its own function */ |
151 /* @todo should put this in its own function */ |
| 126 { /* replace each IMG tag's SRC attribute with an ID attribute. This |
152 { /* replace each IMG tag's SRC attribute with an ID attribute. This |
| 127 actually modifies the contents of str */ |
153 actually modifies the contents of str */ |
| 128 GData *attribs; |
154 GData *attribs; |
| 192 return g_strdup_printf("%03x@%05xmeanwhile", |
218 return g_strdup_printf("%03x@%05xmeanwhile", |
| 193 g_random_int() & 0xfff, g_random_int() & 0xfffff); |
219 g_random_int() & 0xfff, g_random_int() & 0xfffff); |
| 194 } |
220 } |
| 195 |
221 |
| 196 |
222 |
| 197 /** generates a multipart/related content type with a random-ish |
223 /** generates a random-ish boundary value */ |
| 198 boundary value */ |
|
| 199 static char * |
224 static char * |
| 200 im_mime_content_type(void) |
225 im_mime_boundary(void) |
| 201 { |
226 { |
| 202 return g_strdup_printf( |
227 return g_strdup_printf("related_MW%03x_%04x", |
| 203 "multipart/related; boundary=related_MW%03x_%04x", |
|
| 204 g_random_int() & 0xfff, g_random_int() & 0xffff); |
228 g_random_int() & 0xfff, g_random_int() & 0xffff); |
| 205 } |
229 } |
| 206 |
230 |
| 207 /** determine content type from contents */ |
231 /** create MIME image from purple image */ |
| 208 static gchar * |
232 static GMimePart * |
| 209 im_mime_img_content_type(PurpleImage *img) |
233 im_mime_img_to_part(PurpleImage *img) |
| 210 { |
234 { |
| 211 const gchar *mimetype; |
235 const gchar *mimetype; |
| |
236 GMimePart *part; |
| |
237 GByteArray *data; |
| |
238 GMimeStream *stream; |
| |
239 GMimeDataWrapper *wrapper; |
| 212 |
240 |
| 213 mimetype = purple_image_get_mimetype(img); |
241 mimetype = purple_image_get_mimetype(img); |
| 214 if (!mimetype) { |
242 if (mimetype && g_str_has_prefix(mimetype, "image/")) { |
| 215 mimetype = "image"; |
243 mimetype += sizeof("image/") - 1; |
| 216 } |
244 } |
| 217 |
245 |
| 218 return g_strdup_printf("%s; name=\"%s\"", mimetype, |
246 part = g_mime_part_new_with_type("image", mimetype); |
| 219 purple_image_get_friendly_filename(img)); |
247 g_mime_object_set_disposition(GMIME_OBJECT(part), |
| 220 } |
248 GMIME_DISPOSITION_ATTACHMENT); |
| 221 |
249 g_mime_part_set_content_encoding(part, GMIME_CONTENT_ENCODING_BASE64); |
| 222 |
250 g_mime_part_set_filename(part, purple_image_get_friendly_filename(img)); |
| 223 static char * |
251 |
| 224 im_mime_img_content_disp(PurpleImage *img) |
252 data = g_bytes_unref_to_array(purple_image_get_contents(img)); |
| 225 { |
253 stream = g_mime_stream_mem_new_with_byte_array(data); |
| 226 return g_strdup_printf("attachment; filename=\"%s\"", |
254 |
| 227 purple_image_get_friendly_filename(img)); |
255 wrapper = g_mime_data_wrapper_new_with_stream(stream, |
| |
256 GMIME_CONTENT_ENCODING_BINARY); |
| |
257 g_object_unref(G_OBJECT(stream)); |
| |
258 |
| |
259 g_mime_part_set_content(part, wrapper); |
| |
260 g_object_unref(G_OBJECT(wrapper)); |
| |
261 |
| |
262 return part; |
| 228 } |
263 } |
| 229 |
264 |
| 230 |
265 |
| 231 gchar * |
266 gchar * |
| 232 im_mime_generate(const char *message) |
267 im_mime_generate(const char *message) |
| 233 { |
268 { |
| 234 GString *str; |
269 GString *str; |
| 235 PurpleMimeDocument *doc; |
270 GMimeMultipart *doc; |
| 236 PurpleMimePart *part; |
271 GMimeFormatOptions *opts; |
| |
272 GMimeTextPart *text; |
| 237 |
273 |
| 238 GData *attr; |
274 GData *attr; |
| 239 char *tmp, *start, *end; |
275 char *tmp, *start, *end; |
| 240 |
276 |
| 241 str = g_string_new(NULL); |
277 str = g_string_new(NULL); |
| 242 |
278 |
| 243 doc = purple_mime_document_new(); |
279 doc = g_mime_multipart_new_with_subtype("related"); |
| 244 |
280 |
| 245 purple_mime_document_set_field(doc, "Mime-Version", "1.0"); |
281 g_mime_object_set_header(GMIME_OBJECT(doc), "Mime-Version", "1.0", NULL); |
| 246 purple_mime_document_set_field(doc, "Content-Disposition", "inline"); |
282 g_mime_object_set_disposition(GMIME_OBJECT(doc), GMIME_DISPOSITION_INLINE); |
| 247 |
283 |
| 248 tmp = im_mime_content_type(); |
284 tmp = im_mime_boundary(); |
| 249 purple_mime_document_set_field(doc, "Content-Type", tmp); |
285 g_mime_multipart_set_boundary(doc, tmp); |
| 250 g_free(tmp); |
286 g_free(tmp); |
| 251 |
287 |
| 252 tmp = (char *)message; |
288 tmp = (char *)message; |
| 253 while (*tmp && |
289 while (*tmp && |
| 254 purple_markup_find_tag("img", tmp, (const char **) &start, |
290 purple_markup_find_tag("img", tmp, (const char **) &start, |
| 267 if (uri) { |
303 if (uri) { |
| 268 img = purple_image_store_get_from_uri(uri); |
304 img = purple_image_store_get_from_uri(uri); |
| 269 } |
305 } |
| 270 |
306 |
| 271 if (img) { |
307 if (img) { |
| |
308 GMimePart *part; |
| 272 char *cid; |
309 char *cid; |
| 273 gpointer data; |
|
| 274 gsize size; |
|
| 275 |
|
| 276 part = purple_mime_part_new(doc); |
|
| 277 |
|
| 278 data = im_mime_img_content_disp(img); |
|
| 279 purple_mime_part_set_field(part, "Content-Disposition", |
|
| 280 data); |
|
| 281 g_free(data); |
|
| 282 |
|
| 283 data = im_mime_img_content_type(img); |
|
| 284 purple_mime_part_set_field(part, "Content-Type", data); |
|
| 285 g_free(data); |
|
| 286 |
310 |
| 287 cid = im_mime_content_id(); |
311 cid = im_mime_content_id(); |
| 288 data = g_strdup_printf("<%s>", cid); |
312 part = im_mime_img_to_part(img); |
| 289 purple_mime_part_set_field(part, "Content-ID", data); |
313 g_mime_part_set_content_id(part, cid); |
| 290 g_free(data); |
314 g_mime_multipart_add(doc, GMIME_OBJECT(part)); |
| 291 |
315 g_object_unref(G_OBJECT(part)); |
| 292 purple_mime_part_set_field(part, |
|
| 293 "Content-transfer-encoding", "base64"); |
|
| 294 |
|
| 295 /* obtain and base64 encode the image data, and put it |
|
| 296 in the mime part */ |
|
| 297 size = purple_image_get_data_size(img); |
|
| 298 data = g_base64_encode(purple_image_get_data(img), size); |
|
| 299 purple_mime_part_set_data(part, data); |
|
| 300 g_free(data); |
|
| 301 |
316 |
| 302 /* append the modified tag */ |
317 /* append the modified tag */ |
| 303 g_string_append_printf(str, "<img src=\"cid:%s\">", cid); |
318 g_string_append_printf(str, "<img src=\"cid:%s\">", cid); |
| 304 g_free(cid); |
319 g_free(cid); |
| 305 |
320 |
| 316 |
331 |
| 317 /* append left-overs */ |
332 /* append left-overs */ |
| 318 g_string_append(str, tmp); |
333 g_string_append(str, tmp); |
| 319 |
334 |
| 320 /* add the text/html part */ |
335 /* add the text/html part */ |
| 321 part = purple_mime_part_new(doc); |
336 text = g_mime_text_part_new_with_subtype("html"); |
| 322 purple_mime_part_set_field(part, "Content-Disposition", "inline"); |
337 g_mime_object_set_disposition(GMIME_OBJECT(text), |
| |
338 GMIME_DISPOSITION_INLINE); |
| 323 |
339 |
| 324 tmp = purple_utf8_ncr_encode(str->str); |
340 tmp = purple_utf8_ncr_encode(str->str); |
| 325 purple_mime_part_set_field(part, "Content-Type", "text/html"); |
341 g_mime_text_part_set_text(text, tmp); |
| 326 purple_mime_part_set_field(part, "Content-Transfer-Encoding", "7bit"); |
|
| 327 purple_mime_part_set_data(part, tmp); |
|
| 328 g_free(tmp); |
342 g_free(tmp); |
| 329 |
343 |
| |
344 g_mime_multipart_insert(doc, 0, GMIME_OBJECT(text)); |
| |
345 g_object_unref(G_OBJECT(text)); |
| 330 g_string_free(str, TRUE); |
346 g_string_free(str, TRUE); |
| 331 |
347 |
| 332 str = g_string_new(NULL); |
348 opts = g_mime_format_options_new(); |
| 333 purple_mime_document_write(doc, str); |
349 g_mime_format_options_set_newline_format(opts, GMIME_NEWLINE_FORMAT_DOS); |
| 334 return g_string_free(str, FALSE); |
350 tmp = g_mime_object_to_string(GMIME_OBJECT(doc), opts); |
| 335 } |
351 g_mime_format_options_free(opts); |
| |
352 g_object_unref(G_OBJECT(doc)); |
| |
353 return tmp; |
| |
354 } |