Fixes NB#123812, duplicate contacts created when inserting the same email address...
[modest] / src / modest-formatter.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <glib/gi18n.h>
31 #include <string.h>
32 #include <tny-header.h>
33 #include <tny-simple-list.h>
34 #include <tny-gtk-text-buffer-stream.h>
35 #include <tny-camel-mem-stream.h>
36 #include <tny-camel-html-to-text-stream.h>
37 #include "modest-formatter.h"
38 #include "modest-text-utils.h"
39 #include "modest-tny-platform-factory.h"
40 #include <modest-runtime.h>
41
42 #define MAX_BODY_LENGTH 4096
43
44 typedef struct _ModestFormatterPrivate ModestFormatterPrivate;
45 struct _ModestFormatterPrivate {
46         gchar *content_type;
47         gchar *signature;
48 };
49 #define MODEST_FORMATTER_GET_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE((o), \
50                                           MODEST_TYPE_FORMATTER, \
51                                           ModestFormatterPrivate))
52
53 static GObjectClass *parent_class = NULL;
54
55 typedef gchar* FormatterFunc (ModestFormatter *self, const gchar *text, TnyHeader *header, GList *attachments);
56
57 static TnyMsg *modest_formatter_do (ModestFormatter *self, TnyMimePart *body,  TnyHeader *header, 
58                                     FormatterFunc func, GList *attachments);
59
60 static gchar*  modest_formatter_wrapper_cite   (ModestFormatter *self, const gchar *text,
61                                                 TnyHeader *header, GList *attachments);
62 static gchar*  modest_formatter_wrapper_quote  (ModestFormatter *self, const gchar *text,
63                                                 TnyHeader *header, GList *attachments);
64 static gchar*  modest_formatter_wrapper_inline (ModestFormatter *self, const gchar *text,
65                                                 TnyHeader *header, GList *attachments);
66
67 static TnyMimePart *find_body_parent (TnyMimePart *part);
68
69 static gchar *
70 extract_text (ModestFormatter *self, TnyMimePart *body)
71 {
72         TnyStream *mp_stream;
73         TnyStream *stream;
74         TnyStream *input_stream;
75         GtkTextBuffer *buf;
76         GtkTextIter start, end;
77         gchar *text;
78         ModestFormatterPrivate *priv;
79         gint total;
80
81         buf = gtk_text_buffer_new (NULL);
82         stream = TNY_STREAM (tny_gtk_text_buffer_stream_new (buf));
83         tny_stream_reset (stream);
84         mp_stream = tny_mime_part_get_decoded_stream (body);
85
86         if (g_strcmp0 (tny_mime_part_get_content_type (body), "text/html") == 0) {
87                 input_stream = tny_camel_html_to_text_stream_new (mp_stream);
88         } else {
89                 input_stream = g_object_ref (mp_stream);
90         }
91
92         total = 0;
93
94         while (!tny_stream_is_eos (input_stream)) {
95                 gchar buffer [128];
96                 gint n_read;
97                 gint next_read;
98
99                 next_read = MIN (128, MAX_BODY_LENGTH - total);
100                 if (next_read == 0)
101                         break;
102                 n_read = tny_stream_read (input_stream, buffer, next_read);
103                 if (n_read > 0) {
104                         gint n_write;
105                         n_write = tny_stream_write (stream, buffer, n_read);
106                         total += n_write;
107                 } else if (n_read == -1) {
108                         break;
109                 }
110         }
111
112         tny_stream_reset (stream);
113
114         g_object_unref (G_OBJECT(stream));
115         g_object_unref (G_OBJECT (mp_stream));
116         g_object_unref (G_OBJECT (input_stream));
117         
118         gtk_text_buffer_get_bounds (buf, &start, &end);
119         text = gtk_text_buffer_get_text (buf, &start, &end, FALSE);
120         g_object_unref (G_OBJECT(buf));
121
122         /* Convert to desired content type if needed */
123         priv = MODEST_FORMATTER_GET_PRIVATE (self);
124
125         return text;
126 }
127
128 static void
129 construct_from_text (TnyMimePart *part,
130                      const gchar *text,
131                      const gchar *content_type)
132 {
133         TnyStream *text_body_stream;
134
135         /* Create the stream */
136         text_body_stream = TNY_STREAM (tny_camel_mem_stream_new_with_buffer
137                                         (text, strlen(text)));
138
139         /* Construct MIME part */
140         tny_stream_reset (text_body_stream);
141         tny_mime_part_construct (part, text_body_stream, content_type, "7bit");
142         tny_stream_reset (text_body_stream);
143
144         /* Clean */
145         g_object_unref (G_OBJECT (text_body_stream));
146 }
147
148 static TnyMsg *
149 modest_formatter_do (ModestFormatter *self, TnyMimePart *body, TnyHeader *header, FormatterFunc func,
150                      GList *attachments)
151 {
152         TnyMsg *new_msg = NULL;
153         gchar *body_text = NULL, *txt = NULL;
154         ModestFormatterPrivate *priv;
155         TnyMimePart *body_part = NULL;
156
157         g_return_val_if_fail (self, NULL);
158         g_return_val_if_fail (header, NULL);
159         g_return_val_if_fail (func, NULL);
160
161         /* Build new part */
162         new_msg = modest_formatter_create_message (self, TRUE, attachments != NULL, FALSE);
163         body_part = modest_formatter_create_body_part (self, new_msg);
164
165         if (body)
166                 body_text = extract_text (self, body);
167         else
168                 body_text = g_strdup ("");
169
170         txt = (gchar *) func (self, (const gchar*) body_text, header, attachments);
171         priv = MODEST_FORMATTER_GET_PRIVATE (self);
172         construct_from_text (TNY_MIME_PART (body_part), (const gchar*) txt, priv->content_type);
173         g_object_unref (body_part);
174         
175         /* Clean */
176         g_free (body_text);
177         g_free (txt);
178
179         return new_msg;
180 }
181
182 TnyMsg *
183 modest_formatter_cite (ModestFormatter *self, TnyMimePart *body, TnyHeader *header)
184 {
185         return modest_formatter_do (self, body, header, modest_formatter_wrapper_cite, NULL);
186 }
187
188 TnyMsg *
189 modest_formatter_quote (ModestFormatter *self, TnyMimePart *body, TnyHeader *header, GList *attachments)
190 {
191         return modest_formatter_do (self, body, header, modest_formatter_wrapper_quote, attachments);
192 }
193
194 TnyMsg *
195 modest_formatter_inline (ModestFormatter *self, TnyMimePart *body, TnyHeader *header, GList *attachments)
196 {
197         return modest_formatter_do (self, body, header, modest_formatter_wrapper_inline, attachments);
198 }
199
200 TnyMsg *
201 modest_formatter_attach (ModestFormatter *self, TnyMsg *msg, TnyHeader *header)
202 {
203         TnyMsg *new_msg = NULL;
204         TnyMimePart *body_part = NULL;
205         ModestFormatterPrivate *priv;
206         gchar *txt;
207
208         /* Build new part */
209         new_msg     = modest_formatter_create_message (self, TRUE, TRUE, FALSE);
210         body_part = modest_formatter_create_body_part (self, new_msg);
211
212         /* Create the two parts */
213         priv = MODEST_FORMATTER_GET_PRIVATE (self);
214         txt = modest_text_utils_cite ("", priv->content_type, priv->signature,
215                                       NULL, tny_header_get_date_sent (header));
216         construct_from_text (body_part, txt, priv->content_type);
217         g_free (txt);
218         g_object_unref (body_part);
219
220         if (msg) {
221                 /* Add parts */
222                 tny_mime_part_add_part (TNY_MIME_PART (new_msg), TNY_MIME_PART (msg));
223         }
224
225         return new_msg;
226 }
227
228 ModestFormatter*
229 modest_formatter_new (const gchar *content_type, const gchar *signature)
230 {
231         ModestFormatter *formatter;
232         ModestFormatterPrivate *priv;
233
234         formatter = g_object_new (MODEST_TYPE_FORMATTER, NULL);
235         priv = MODEST_FORMATTER_GET_PRIVATE (formatter);
236         priv->content_type = g_strdup (content_type);
237         priv->signature = g_strdup (signature);
238
239         return formatter;
240 }
241
242 static void
243 modest_formatter_instance_init (GTypeInstance *instance, gpointer g_class)
244 {
245         ModestFormatter *self = (ModestFormatter *)instance;
246         ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
247
248         priv->content_type = NULL;
249         priv->signature = NULL;
250 }
251
252 static void
253 modest_formatter_finalize (GObject *object)
254 {
255         ModestFormatter *self = (ModestFormatter *)object;
256         ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
257
258         if (priv->content_type)
259                 g_free (priv->content_type);
260
261         if (priv->signature)
262                 g_free (priv->signature);
263
264         (*parent_class->finalize) (object);
265 }
266
267 static void 
268 modest_formatter_class_init (ModestFormatterClass *class)
269 {
270         GObjectClass *object_class;
271
272         parent_class = g_type_class_peek_parent (class);
273         object_class = (GObjectClass*) class;
274         object_class->finalize = modest_formatter_finalize;
275
276         g_type_class_add_private (object_class, sizeof (ModestFormatterPrivate));
277 }
278
279 GType 
280 modest_formatter_get_type (void)
281 {
282         static GType type = 0;
283
284         if (G_UNLIKELY(type == 0))
285         {
286                 static const GTypeInfo info = 
287                 {
288                   sizeof (ModestFormatterClass),
289                   NULL,   /* base_init */
290                   NULL,   /* base_finalize */
291                   (GClassInitFunc) modest_formatter_class_init,   /* class_init */
292                   NULL,   /* class_finalize */
293                   NULL,   /* class_data */
294                   sizeof (ModestFormatter),
295                   0,      /* n_preallocs */
296                   modest_formatter_instance_init    /* instance_init */
297                 };
298                 
299                 type = g_type_register_static (G_TYPE_OBJECT,
300                         "ModestFormatter",
301                         &info, 0);
302         }
303
304         return type;
305 }
306
307 /****************/
308 static gchar *
309 modest_formatter_wrapper_cite (ModestFormatter *self, const gchar *text, TnyHeader *header,
310                                GList *attachments) 
311 {
312         gchar *result, *from;
313         ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
314         
315         from = tny_header_dup_from (header);
316         result = modest_text_utils_cite (text, 
317                                          priv->content_type, 
318                                          priv->signature,
319                                          from, 
320                                          tny_header_get_date_sent (header));
321         g_free (from);
322         return result;
323 }
324
325 static gchar *
326 modest_formatter_wrapper_inline (ModestFormatter *self, const gchar *text, TnyHeader *header,
327                                  GList *attachments) 
328 {
329         gchar *result, *from, *to, *subject;
330         ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
331
332         from = tny_header_dup_from (header);
333         to = tny_header_dup_to (header);
334         subject = tny_header_dup_subject (header);
335         result =  modest_text_utils_inline (text, 
336                                             priv->content_type, 
337                                             priv->signature,
338                                             from,
339                                             tny_header_get_date_sent (header),
340                                             to,
341                                             subject);
342         g_free (subject);
343         g_free (to);
344         g_free (from);
345         return result;
346 }
347
348 static gchar *
349 modest_formatter_wrapper_quote (ModestFormatter *self, const gchar *text, TnyHeader *header,
350                                 GList *attachments) 
351 {
352         ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
353         GList *filenames = NULL;
354         GList *node = NULL;
355         gchar *result = NULL;
356         gchar *from;
357
358         /* First we need a GList of attachments filenames */
359         for (node = attachments; node != NULL; node = g_list_next (node)) {
360                 TnyMimePart *part = (TnyMimePart *) node->data;
361                 gchar *filename = NULL;
362                 if (TNY_IS_MSG (part)) {
363                         TnyHeader *header = tny_msg_get_header (TNY_MSG (part));
364                         filename = tny_header_dup_subject (header);
365                         if ((filename == NULL)||(filename[0] == '\0')) {
366                                 g_free (filename);
367                                 filename = g_strdup (_("mail_va_no_subject"));
368                         }
369                         g_object_unref (header);
370                 } else {
371                         filename = g_strdup (tny_mime_part_get_filename (part));
372                         if ((filename == NULL)||(filename[0] == '\0')) {
373                                 g_free (filename);
374                                 filename = g_strdup ("");
375                         }
376                 }
377                 filenames = g_list_prepend (filenames, filename);
378         }
379
380         /* TODO: get 80 from the configuration */
381         from = tny_header_dup_from (header);
382         result = modest_text_utils_quote (text, 
383                                           priv->content_type, 
384                                           priv->signature,
385                                           from,
386                                           tny_header_get_date_sent (header),
387                                           filenames,
388                                           80);
389         g_free (from);
390
391         g_list_foreach (filenames, (GFunc) g_free, NULL);
392         g_list_free (filenames);
393         return result;
394 }
395
396 TnyMsg * 
397 modest_formatter_create_message (ModestFormatter *self, gboolean single_body, 
398                                  gboolean has_attachments, gboolean has_images)
399 {
400         TnyMsg *result = NULL;
401         TnyPlatformFactory *fact = NULL;
402         TnyMimePart *body_mime_part = NULL;
403         TnyMimePart *related_mime_part = NULL;
404
405         fact    = modest_runtime_get_platform_factory ();
406         result = tny_platform_factory_new_msg (fact);
407         if (has_attachments) {
408                 tny_mime_part_set_content_type (TNY_MIME_PART (result), "multipart/mixed");
409                 if (has_images) {
410                         related_mime_part = tny_platform_factory_new_mime_part (fact);
411                         tny_mime_part_set_content_type (related_mime_part, "multipart/related");
412                         tny_mime_part_add_part (TNY_MIME_PART (result), related_mime_part);
413                 } else {
414                         related_mime_part = g_object_ref (result);
415                 }
416                         
417                 if (!single_body) {
418                         body_mime_part = tny_platform_factory_new_mime_part (fact);
419                         tny_mime_part_set_content_type (body_mime_part, "multipart/alternative");
420                         tny_mime_part_add_part (TNY_MIME_PART (related_mime_part), body_mime_part);
421                         g_object_unref (body_mime_part);
422                 }
423
424                 g_object_unref (related_mime_part);
425         } else if (has_images) {
426                 tny_mime_part_set_content_type (TNY_MIME_PART (result), "multipart/related");
427
428                 if (!single_body) {
429                         body_mime_part = tny_platform_factory_new_mime_part (fact);
430                         tny_mime_part_set_content_type (body_mime_part, "multipart/alternative");
431                         tny_mime_part_add_part (TNY_MIME_PART (result), body_mime_part);
432                         g_object_unref (body_mime_part);
433                 }
434
435         } else if (!single_body) {
436                 tny_mime_part_set_content_type (TNY_MIME_PART (result), "multipart/alternative");
437         }
438
439         return result;
440 }
441
442 TnyMimePart *
443 find_body_parent (TnyMimePart *part)
444 {
445         const gchar *msg_content_type = NULL;
446         msg_content_type = tny_mime_part_get_content_type (part);
447
448         if ((msg_content_type != NULL) &&
449             (!g_strcasecmp (msg_content_type, "multipart/alternative")))
450                 return g_object_ref (part);
451         else if ((msg_content_type != NULL) &&
452                  (g_str_has_prefix (msg_content_type, "multipart/"))) {
453                 TnyIterator *iter = NULL;
454                 TnyMimePart *alternative_part = NULL;
455                 TnyMimePart *related_part = NULL;
456                 TnyList *parts = TNY_LIST (tny_simple_list_new ());
457                 tny_mime_part_get_parts (TNY_MIME_PART (part), parts);
458                 iter = tny_list_create_iterator (parts);
459
460                 while (!tny_iterator_is_done (iter)) {
461                         TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
462                         if (part && !g_strcasecmp(tny_mime_part_get_content_type (part), "multipart/alternative")) {
463                                 alternative_part = part;
464                                 break;
465                         } else if (part && !g_strcasecmp (tny_mime_part_get_content_type (part), "multipart/related")) {
466                                 related_part = part;
467                                 break;
468                         }
469
470                         if (part)
471                                 g_object_unref (part);
472
473                         tny_iterator_next (iter);
474                 }
475                 g_object_unref (iter);
476                 g_object_unref (parts);
477                 if (related_part) {
478                         TnyMimePart *result;
479                         result = find_body_parent (related_part);
480                         g_object_unref (related_part);
481                         return result;
482                 } else if (alternative_part)
483                         return alternative_part;
484                 else 
485                         return g_object_ref (part);
486         } else
487                 return NULL;
488 }
489
490 TnyMimePart * 
491 modest_formatter_create_body_part (ModestFormatter *self, TnyMsg *msg)
492 {
493         TnyMimePart *result = NULL;
494         TnyPlatformFactory *fact = NULL;
495         TnyMimePart *parent = NULL;
496
497         parent = find_body_parent (TNY_MIME_PART (msg));
498         fact = modest_runtime_get_platform_factory ();
499         if (parent != NULL) {
500                 result = tny_platform_factory_new_mime_part (fact);
501                 tny_mime_part_add_part (TNY_MIME_PART (parent), result);
502                 g_object_unref (parent);
503         } else {
504                 result = g_object_ref (msg);
505         }
506
507         return result;
508
509 }