1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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.
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.
30 #include <glib/gi18n.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>
43 #define MAX_BODY_LINES 1024
44 #define MAX_BODY_LENGTH 1024*128
46 typedef struct _ModestFormatterPrivate ModestFormatterPrivate;
47 struct _ModestFormatterPrivate {
51 #define MODEST_FORMATTER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
52 MODEST_TYPE_FORMATTER, \
53 ModestFormatterPrivate))
55 static GObjectClass *parent_class = NULL;
57 typedef gchar* FormatterFunc (ModestFormatter *self, const gchar *text, TnyHeader *header, GList *attachments);
59 static TnyMsg *modest_formatter_do (ModestFormatter *self, TnyMimePart *body, TnyHeader *header,
60 FormatterFunc func, GList *attachments);
62 static gchar* modest_formatter_wrapper_cite (ModestFormatter *self, const gchar *text,
63 TnyHeader *header, GList *attachments);
64 static gchar* modest_formatter_wrapper_quote (ModestFormatter *self, const gchar *text,
65 TnyHeader *header, GList *attachments);
66 static gchar* modest_formatter_wrapper_inline (ModestFormatter *self, const gchar *text,
67 TnyHeader *header, GList *attachments);
69 static TnyMimePart *find_body_parent (TnyMimePart *part);
72 count_end_tag_lines (const gchar *haystack, const gchar *needle)
77 tmp = g_strstr_len (haystack, g_utf8_strlen (haystack, -1), ">\n");
78 while (tmp && (tmp <= needle)) {
81 tmp = g_strstr_len (tmp, g_utf8_strlen (tmp, -1), ">\n");
88 extract_text (ModestFormatter *self, TnyMimePart *body)
92 TnyStream *input_stream;
94 GtkTextIter start, end;
96 ModestFormatterPrivate *priv;
97 gint total, lines, total_lines, line_chars;
98 gboolean is_html, first_time;
100 buf = gtk_text_buffer_new (NULL);
101 stream = TNY_STREAM (tny_gtk_text_buffer_stream_new (buf));
102 tny_stream_reset (stream);
103 mp_stream = tny_mime_part_get_decoded_stream (body);
105 is_html = (g_strcmp0 (tny_mime_part_get_content_type (body), "text/html") == 0);
107 input_stream = tny_camel_html_to_text_stream_new (mp_stream);
109 input_stream = g_object_ref (mp_stream);
117 /* For pure HTML emails tny_camel_html_to_text_stream inserts
118 a \n for every ">\n" found in the email including the HTML
119 headers (<html>, <head> ...). For that reason we need to
120 remove them from the resulting text as it is artificially
121 added by the stream */
123 const guint BUFFER_SIZE = 1024;
125 gboolean look_for_end_tag, found;
126 gchar buffer [BUFFER_SIZE + 1];
129 is = g_object_ref (mp_stream);
130 look_for_end_tag = FALSE;
133 /* This algorithm does not work if the body tag is
134 spread along 2 different stream reads. But there
135 are not a lot of changes for this to happen as the
136 buffer size is big enough in most situations. In
137 the worst case, when it's not found we just accept
138 the original translation with the extra "\n" */
139 while (!tny_stream_is_eos (is) && !found) {
143 memset (buffer, 0, BUFFER_SIZE);
144 n_read = tny_stream_read (is, buffer, BUFFER_SIZE);
146 if (G_UNLIKELY (n_read < 0))
149 buffer[n_read] = '\0';
151 /* If we found body,then look for the end of the tag */
152 if (look_for_end_tag) {
153 needle = strchr (buffer, '>');
157 lines += count_end_tag_lines (buffer, needle);
163 /* Try to find the <body> tag. There
164 is no other HTML tag starting by
165 "bo", and we can detect more cases
166 were <body> tag falls into two
167 different stream reads */
168 needle = g_strstr_len (buffer, n_read, "<bo");
171 look_for_end_tag = TRUE;
173 needle = &(buffer[n_read]);
175 lines += count_end_tag_lines (buffer, needle);
177 closing = strchr (needle, '>');
179 if (*(closing + 1) == '\n')
188 tny_stream_reset (is);
194 while (!tny_stream_is_eos (input_stream)) {
200 next_read = MIN (128, MAX_BODY_LENGTH - total);
203 n_read = tny_stream_read (input_stream, buffer, next_read);
205 if (G_UNLIKELY (n_read < 0))
209 while (offset < buffer + n_read) {
211 if (*offset == '\n') {
216 if (line_chars >= LINE_WRAP) {
221 if (total_lines >= MAX_BODY_LINES)
226 if (offset - buffer > 0) {
227 gint n_write = 0, to_write = 0;
230 /* Discard lines artificially inserted by
231 Camel when translating from HTML to
232 text. Do it only for the first read */
234 if (G_UNLIKELY (first_time) && lines) {
236 for (i=0; i < lines; i++) {
237 buffer_ptr = strchr (buffer_ptr, '\n');
242 to_write = offset - buffer_ptr;
243 n_write = tny_stream_write (stream, buffer_ptr, to_write);
245 } else if (n_read == -1) {
249 if (total_lines >= MAX_BODY_LINES)
253 tny_stream_reset (stream);
255 g_object_unref (G_OBJECT(stream));
256 g_object_unref (G_OBJECT (mp_stream));
257 g_object_unref (G_OBJECT (input_stream));
259 gtk_text_buffer_get_bounds (buf, &start, &end);
260 text = gtk_text_buffer_get_text (buf, &start, &end, FALSE);
261 g_object_unref (G_OBJECT(buf));
263 /* Convert to desired content type if needed */
264 priv = MODEST_FORMATTER_GET_PRIVATE (self);
270 construct_from_text (TnyMimePart *part,
272 const gchar *content_type)
274 TnyStream *text_body_stream;
276 /* Create the stream */
277 text_body_stream = TNY_STREAM (tny_camel_mem_stream_new_with_buffer
278 (text, strlen(text)));
280 /* Construct MIME part */
281 tny_stream_reset (text_body_stream);
282 tny_mime_part_construct (part, text_body_stream, content_type, "7bit");
283 tny_stream_reset (text_body_stream);
286 g_object_unref (G_OBJECT (text_body_stream));
290 modest_formatter_do (ModestFormatter *self, TnyMimePart *body, TnyHeader *header, FormatterFunc func,
293 TnyMsg *new_msg = NULL;
294 gchar *body_text = NULL, *txt = NULL;
295 ModestFormatterPrivate *priv;
296 TnyMimePart *body_part = NULL;
298 g_return_val_if_fail (self, NULL);
299 g_return_val_if_fail (header, NULL);
300 g_return_val_if_fail (func, NULL);
303 new_msg = modest_formatter_create_message (self, TRUE, attachments != NULL, FALSE);
304 body_part = modest_formatter_create_body_part (self, new_msg);
307 body_text = extract_text (self, body);
309 body_text = g_strdup ("");
311 txt = (gchar *) func (self, (const gchar*) body_text, header, attachments);
312 priv = MODEST_FORMATTER_GET_PRIVATE (self);
313 construct_from_text (TNY_MIME_PART (body_part), (const gchar*) txt, priv->content_type);
314 g_object_unref (body_part);
324 modest_formatter_cite (ModestFormatter *self, TnyMimePart *body, TnyHeader *header)
326 return modest_formatter_do (self, body, header, modest_formatter_wrapper_cite, NULL);
330 modest_formatter_quote (ModestFormatter *self, TnyMimePart *body, TnyHeader *header, GList *attachments)
332 return modest_formatter_do (self, body, header, modest_formatter_wrapper_quote, attachments);
336 modest_formatter_inline (ModestFormatter *self, TnyMimePart *body, TnyHeader *header, GList *attachments)
338 return modest_formatter_do (self, body, header, modest_formatter_wrapper_inline, attachments);
342 modest_formatter_attach (ModestFormatter *self, TnyMsg *msg, TnyHeader *header)
344 TnyMsg *new_msg = NULL;
345 TnyMimePart *body_part = NULL;
346 ModestFormatterPrivate *priv;
350 new_msg = modest_formatter_create_message (self, TRUE, TRUE, FALSE);
351 body_part = modest_formatter_create_body_part (self, new_msg);
353 /* Create the two parts */
354 priv = MODEST_FORMATTER_GET_PRIVATE (self);
355 txt = modest_text_utils_cite ("", priv->content_type, priv->signature,
356 NULL, tny_header_get_date_sent (header));
357 construct_from_text (body_part, txt, priv->content_type);
359 g_object_unref (body_part);
363 tny_mime_part_add_part (TNY_MIME_PART (new_msg), TNY_MIME_PART (msg));
370 modest_formatter_new (const gchar *content_type, const gchar *signature)
372 ModestFormatter *formatter;
373 ModestFormatterPrivate *priv;
375 formatter = g_object_new (MODEST_TYPE_FORMATTER, NULL);
376 priv = MODEST_FORMATTER_GET_PRIVATE (formatter);
377 priv->content_type = g_strdup (content_type);
378 priv->signature = g_strdup (signature);
384 modest_formatter_instance_init (GTypeInstance *instance, gpointer g_class)
386 ModestFormatter *self = (ModestFormatter *)instance;
387 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
389 priv->content_type = NULL;
390 priv->signature = NULL;
394 modest_formatter_finalize (GObject *object)
396 ModestFormatter *self = (ModestFormatter *)object;
397 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
399 if (priv->content_type)
400 g_free (priv->content_type);
403 g_free (priv->signature);
405 (*parent_class->finalize) (object);
409 modest_formatter_class_init (ModestFormatterClass *class)
411 GObjectClass *object_class;
413 parent_class = g_type_class_peek_parent (class);
414 object_class = (GObjectClass*) class;
415 object_class->finalize = modest_formatter_finalize;
417 g_type_class_add_private (object_class, sizeof (ModestFormatterPrivate));
421 modest_formatter_get_type (void)
423 static GType type = 0;
425 if (G_UNLIKELY(type == 0))
427 static const GTypeInfo info =
429 sizeof (ModestFormatterClass),
430 NULL, /* base_init */
431 NULL, /* base_finalize */
432 (GClassInitFunc) modest_formatter_class_init, /* class_init */
433 NULL, /* class_finalize */
434 NULL, /* class_data */
435 sizeof (ModestFormatter),
437 modest_formatter_instance_init /* instance_init */
440 type = g_type_register_static (G_TYPE_OBJECT,
450 modest_formatter_wrapper_cite (ModestFormatter *self, const gchar *text, TnyHeader *header,
453 gchar *result, *from;
454 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
456 from = tny_header_dup_from (header);
457 result = modest_text_utils_cite (text,
461 tny_header_get_date_sent (header));
467 modest_formatter_wrapper_inline (ModestFormatter *self, const gchar *text, TnyHeader *header,
470 gchar *result, *from, *to, *subject;
471 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
473 from = tny_header_dup_from (header);
474 to = tny_header_dup_to (header);
475 subject = tny_header_dup_subject (header);
476 result = modest_text_utils_inline (text,
480 tny_header_get_date_sent (header),
490 modest_formatter_wrapper_quote (ModestFormatter *self, const gchar *text, TnyHeader *header,
493 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
494 GList *filenames = NULL;
496 gchar *result = NULL;
499 /* First we need a GList of attachments filenames */
500 for (node = attachments; node != NULL; node = g_list_next (node)) {
501 TnyMimePart *part = (TnyMimePart *) node->data;
502 gchar *filename = NULL;
503 if (TNY_IS_MSG (part)) {
504 TnyHeader *header = tny_msg_get_header (TNY_MSG (part));
505 filename = tny_header_dup_subject (header);
506 if ((filename == NULL)||(filename[0] == '\0')) {
508 filename = g_strdup (_("mail_va_no_subject"));
510 g_object_unref (header);
512 filename = g_strdup (tny_mime_part_get_filename (part));
513 if ((filename == NULL)||(filename[0] == '\0')) {
515 filename = g_strdup ("");
518 filenames = g_list_prepend (filenames, filename);
521 /* TODO: get 80 from the configuration */
522 from = tny_header_dup_from (header);
523 result = modest_text_utils_quote (text,
527 tny_header_get_date_sent (header),
532 g_list_foreach (filenames, (GFunc) g_free, NULL);
533 g_list_free (filenames);
538 modest_formatter_create_message (ModestFormatter *self, gboolean single_body,
539 gboolean has_attachments, gboolean has_images)
541 TnyMsg *result = NULL;
542 TnyPlatformFactory *fact = NULL;
543 TnyMimePart *body_mime_part = NULL;
544 TnyMimePart *related_mime_part = NULL;
546 fact = modest_runtime_get_platform_factory ();
547 result = tny_platform_factory_new_msg (fact);
548 if (has_attachments) {
549 tny_mime_part_set_content_type (TNY_MIME_PART (result), "multipart/mixed");
551 related_mime_part = tny_platform_factory_new_mime_part (fact);
552 tny_mime_part_set_content_type (related_mime_part, "multipart/related");
553 tny_mime_part_add_part (TNY_MIME_PART (result), related_mime_part);
555 related_mime_part = g_object_ref (result);
559 body_mime_part = tny_platform_factory_new_mime_part (fact);
560 tny_mime_part_set_content_type (body_mime_part, "multipart/alternative");
561 tny_mime_part_add_part (TNY_MIME_PART (related_mime_part), body_mime_part);
562 g_object_unref (body_mime_part);
565 g_object_unref (related_mime_part);
566 } else if (has_images) {
567 tny_mime_part_set_content_type (TNY_MIME_PART (result), "multipart/related");
570 body_mime_part = tny_platform_factory_new_mime_part (fact);
571 tny_mime_part_set_content_type (body_mime_part, "multipart/alternative");
572 tny_mime_part_add_part (TNY_MIME_PART (result), body_mime_part);
573 g_object_unref (body_mime_part);
576 } else if (!single_body) {
577 tny_mime_part_set_content_type (TNY_MIME_PART (result), "multipart/alternative");
584 find_body_parent (TnyMimePart *part)
586 const gchar *msg_content_type = NULL;
587 msg_content_type = tny_mime_part_get_content_type (part);
589 if ((msg_content_type != NULL) &&
590 (!g_ascii_strcasecmp (msg_content_type, "multipart/alternative")))
591 return g_object_ref (part);
592 else if ((msg_content_type != NULL) &&
593 (g_str_has_prefix (msg_content_type, "multipart/"))) {
594 TnyIterator *iter = NULL;
595 TnyMimePart *alternative_part = NULL;
596 TnyMimePart *related_part = NULL;
597 TnyList *parts = TNY_LIST (tny_simple_list_new ());
598 tny_mime_part_get_parts (TNY_MIME_PART (part), parts);
599 iter = tny_list_create_iterator (parts);
601 while (!tny_iterator_is_done (iter)) {
602 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
603 if (part && !g_ascii_strcasecmp(tny_mime_part_get_content_type (part), "multipart/alternative")) {
604 alternative_part = part;
606 } else if (part && !g_ascii_strcasecmp (tny_mime_part_get_content_type (part), "multipart/related")) {
612 g_object_unref (part);
614 tny_iterator_next (iter);
616 g_object_unref (iter);
617 g_object_unref (parts);
620 result = find_body_parent (related_part);
621 g_object_unref (related_part);
623 } else if (alternative_part)
624 return alternative_part;
626 return g_object_ref (part);
632 modest_formatter_create_body_part (ModestFormatter *self, TnyMsg *msg)
634 TnyMimePart *result = NULL;
635 TnyPlatformFactory *fact = NULL;
636 TnyMimePart *parent = NULL;
638 parent = find_body_parent (TNY_MIME_PART (msg));
639 fact = modest_runtime_get_platform_factory ();
640 if (parent != NULL) {
641 result = tny_platform_factory_new_mime_part (fact);
642 tny_mime_part_add_part (TNY_MIME_PART (parent), result);
643 g_object_unref (parent);
645 result = g_object_ref (msg);