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;
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);
193 while (!tny_stream_is_eos (input_stream)) {
199 next_read = MIN (128, MAX_BODY_LENGTH - total);
202 n_read = tny_stream_read (input_stream, buffer, next_read);
204 if (G_UNLIKELY (n_read < 0))
208 while (offset < buffer + n_read) {
210 if (*offset == '\n') {
215 if (line_chars >= LINE_WRAP) {
220 if (total_lines >= MAX_BODY_LINES)
225 if (offset - buffer > 0) {
226 gint n_write = 0, to_write = 0;
229 /* Discard lines artificially inserted by
230 Camel when translating from HTML to text */
234 for (i=0; i < lines; i++) {
235 buffer_ptr = strchr (buffer_ptr, '\n');
239 to_write = offset - buffer_ptr;
240 n_write = tny_stream_write (stream, buffer_ptr, to_write);
242 } else if (n_read == -1) {
246 if (total_lines >= MAX_BODY_LINES)
250 tny_stream_reset (stream);
252 g_object_unref (G_OBJECT(stream));
253 g_object_unref (G_OBJECT (mp_stream));
254 g_object_unref (G_OBJECT (input_stream));
256 gtk_text_buffer_get_bounds (buf, &start, &end);
257 text = gtk_text_buffer_get_text (buf, &start, &end, FALSE);
258 g_object_unref (G_OBJECT(buf));
260 /* Convert to desired content type if needed */
261 priv = MODEST_FORMATTER_GET_PRIVATE (self);
267 construct_from_text (TnyMimePart *part,
269 const gchar *content_type)
271 TnyStream *text_body_stream;
273 /* Create the stream */
274 text_body_stream = TNY_STREAM (tny_camel_mem_stream_new_with_buffer
275 (text, strlen(text)));
277 /* Construct MIME part */
278 tny_stream_reset (text_body_stream);
279 tny_mime_part_construct (part, text_body_stream, content_type, "7bit");
280 tny_stream_reset (text_body_stream);
283 g_object_unref (G_OBJECT (text_body_stream));
287 modest_formatter_do (ModestFormatter *self, TnyMimePart *body, TnyHeader *header, FormatterFunc func,
290 TnyMsg *new_msg = NULL;
291 gchar *body_text = NULL, *txt = NULL;
292 ModestFormatterPrivate *priv;
293 TnyMimePart *body_part = NULL;
295 g_return_val_if_fail (self, NULL);
296 g_return_val_if_fail (header, NULL);
297 g_return_val_if_fail (func, NULL);
300 new_msg = modest_formatter_create_message (self, TRUE, attachments != NULL, FALSE);
301 body_part = modest_formatter_create_body_part (self, new_msg);
304 body_text = extract_text (self, body);
306 body_text = g_strdup ("");
308 txt = (gchar *) func (self, (const gchar*) body_text, header, attachments);
309 priv = MODEST_FORMATTER_GET_PRIVATE (self);
310 construct_from_text (TNY_MIME_PART (body_part), (const gchar*) txt, priv->content_type);
311 g_object_unref (body_part);
321 modest_formatter_cite (ModestFormatter *self, TnyMimePart *body, TnyHeader *header)
323 return modest_formatter_do (self, body, header, modest_formatter_wrapper_cite, NULL);
327 modest_formatter_quote (ModestFormatter *self, TnyMimePart *body, TnyHeader *header, GList *attachments)
329 return modest_formatter_do (self, body, header, modest_formatter_wrapper_quote, attachments);
333 modest_formatter_inline (ModestFormatter *self, TnyMimePart *body, TnyHeader *header, GList *attachments)
335 return modest_formatter_do (self, body, header, modest_formatter_wrapper_inline, attachments);
339 modest_formatter_attach (ModestFormatter *self, TnyMsg *msg, TnyHeader *header)
341 TnyMsg *new_msg = NULL;
342 TnyMimePart *body_part = NULL;
343 ModestFormatterPrivate *priv;
347 new_msg = modest_formatter_create_message (self, TRUE, TRUE, FALSE);
348 body_part = modest_formatter_create_body_part (self, new_msg);
350 /* Create the two parts */
351 priv = MODEST_FORMATTER_GET_PRIVATE (self);
352 txt = modest_text_utils_cite ("", priv->content_type, priv->signature,
353 NULL, tny_header_get_date_sent (header));
354 construct_from_text (body_part, txt, priv->content_type);
356 g_object_unref (body_part);
360 tny_mime_part_add_part (TNY_MIME_PART (new_msg), TNY_MIME_PART (msg));
367 modest_formatter_new (const gchar *content_type, const gchar *signature)
369 ModestFormatter *formatter;
370 ModestFormatterPrivate *priv;
372 formatter = g_object_new (MODEST_TYPE_FORMATTER, NULL);
373 priv = MODEST_FORMATTER_GET_PRIVATE (formatter);
374 priv->content_type = g_strdup (content_type);
375 priv->signature = g_strdup (signature);
381 modest_formatter_instance_init (GTypeInstance *instance, gpointer g_class)
383 ModestFormatter *self = (ModestFormatter *)instance;
384 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
386 priv->content_type = NULL;
387 priv->signature = NULL;
391 modest_formatter_finalize (GObject *object)
393 ModestFormatter *self = (ModestFormatter *)object;
394 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
396 if (priv->content_type)
397 g_free (priv->content_type);
400 g_free (priv->signature);
402 (*parent_class->finalize) (object);
406 modest_formatter_class_init (ModestFormatterClass *class)
408 GObjectClass *object_class;
410 parent_class = g_type_class_peek_parent (class);
411 object_class = (GObjectClass*) class;
412 object_class->finalize = modest_formatter_finalize;
414 g_type_class_add_private (object_class, sizeof (ModestFormatterPrivate));
418 modest_formatter_get_type (void)
420 static GType type = 0;
422 if (G_UNLIKELY(type == 0))
424 static const GTypeInfo info =
426 sizeof (ModestFormatterClass),
427 NULL, /* base_init */
428 NULL, /* base_finalize */
429 (GClassInitFunc) modest_formatter_class_init, /* class_init */
430 NULL, /* class_finalize */
431 NULL, /* class_data */
432 sizeof (ModestFormatter),
434 modest_formatter_instance_init /* instance_init */
437 type = g_type_register_static (G_TYPE_OBJECT,
447 modest_formatter_wrapper_cite (ModestFormatter *self, const gchar *text, TnyHeader *header,
450 gchar *result, *from;
451 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
453 from = tny_header_dup_from (header);
454 result = modest_text_utils_cite (text,
458 tny_header_get_date_sent (header));
464 modest_formatter_wrapper_inline (ModestFormatter *self, const gchar *text, TnyHeader *header,
467 gchar *result, *from, *to, *subject;
468 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
470 from = tny_header_dup_from (header);
471 to = tny_header_dup_to (header);
472 subject = tny_header_dup_subject (header);
473 result = modest_text_utils_inline (text,
477 tny_header_get_date_sent (header),
487 modest_formatter_wrapper_quote (ModestFormatter *self, const gchar *text, TnyHeader *header,
490 ModestFormatterPrivate *priv = MODEST_FORMATTER_GET_PRIVATE (self);
491 GList *filenames = NULL;
493 gchar *result = NULL;
496 /* First we need a GList of attachments filenames */
497 for (node = attachments; node != NULL; node = g_list_next (node)) {
498 TnyMimePart *part = (TnyMimePart *) node->data;
499 gchar *filename = NULL;
500 if (TNY_IS_MSG (part)) {
501 TnyHeader *header = tny_msg_get_header (TNY_MSG (part));
502 filename = tny_header_dup_subject (header);
503 if ((filename == NULL)||(filename[0] == '\0')) {
505 filename = g_strdup (_("mail_va_no_subject"));
507 g_object_unref (header);
509 filename = g_strdup (tny_mime_part_get_filename (part));
510 if ((filename == NULL)||(filename[0] == '\0')) {
512 filename = g_strdup ("");
515 filenames = g_list_prepend (filenames, filename);
518 /* TODO: get 80 from the configuration */
519 from = tny_header_dup_from (header);
520 result = modest_text_utils_quote (text,
524 tny_header_get_date_sent (header),
529 g_list_foreach (filenames, (GFunc) g_free, NULL);
530 g_list_free (filenames);
535 modest_formatter_create_message (ModestFormatter *self, gboolean single_body,
536 gboolean has_attachments, gboolean has_images)
538 TnyMsg *result = NULL;
539 TnyPlatformFactory *fact = NULL;
540 TnyMimePart *body_mime_part = NULL;
541 TnyMimePart *related_mime_part = NULL;
543 fact = modest_runtime_get_platform_factory ();
544 result = tny_platform_factory_new_msg (fact);
545 if (has_attachments) {
546 tny_mime_part_set_content_type (TNY_MIME_PART (result), "multipart/mixed");
548 related_mime_part = tny_platform_factory_new_mime_part (fact);
549 tny_mime_part_set_content_type (related_mime_part, "multipart/related");
550 tny_mime_part_add_part (TNY_MIME_PART (result), related_mime_part);
552 related_mime_part = g_object_ref (result);
556 body_mime_part = tny_platform_factory_new_mime_part (fact);
557 tny_mime_part_set_content_type (body_mime_part, "multipart/alternative");
558 tny_mime_part_add_part (TNY_MIME_PART (related_mime_part), body_mime_part);
559 g_object_unref (body_mime_part);
562 g_object_unref (related_mime_part);
563 } else if (has_images) {
564 tny_mime_part_set_content_type (TNY_MIME_PART (result), "multipart/related");
567 body_mime_part = tny_platform_factory_new_mime_part (fact);
568 tny_mime_part_set_content_type (body_mime_part, "multipart/alternative");
569 tny_mime_part_add_part (TNY_MIME_PART (result), body_mime_part);
570 g_object_unref (body_mime_part);
573 } else if (!single_body) {
574 tny_mime_part_set_content_type (TNY_MIME_PART (result), "multipart/alternative");
581 find_body_parent (TnyMimePart *part)
583 const gchar *msg_content_type = NULL;
584 msg_content_type = tny_mime_part_get_content_type (part);
586 if ((msg_content_type != NULL) &&
587 (!g_ascii_strcasecmp (msg_content_type, "multipart/alternative")))
588 return g_object_ref (part);
589 else if ((msg_content_type != NULL) &&
590 (g_str_has_prefix (msg_content_type, "multipart/"))) {
591 TnyIterator *iter = NULL;
592 TnyMimePart *alternative_part = NULL;
593 TnyMimePart *related_part = NULL;
594 TnyList *parts = TNY_LIST (tny_simple_list_new ());
595 tny_mime_part_get_parts (TNY_MIME_PART (part), parts);
596 iter = tny_list_create_iterator (parts);
598 while (!tny_iterator_is_done (iter)) {
599 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
600 if (part && !g_ascii_strcasecmp(tny_mime_part_get_content_type (part), "multipart/alternative")) {
601 alternative_part = part;
603 } else if (part && !g_ascii_strcasecmp (tny_mime_part_get_content_type (part), "multipart/related")) {
609 g_object_unref (part);
611 tny_iterator_next (iter);
613 g_object_unref (iter);
614 g_object_unref (parts);
617 result = find_body_parent (related_part);
618 g_object_unref (related_part);
620 } else if (alternative_part)
621 return alternative_part;
623 return g_object_ref (part);
629 modest_formatter_create_body_part (ModestFormatter *self, TnyMsg *msg)
631 TnyMimePart *result = NULL;
632 TnyPlatformFactory *fact = NULL;
633 TnyMimePart *parent = NULL;
635 parent = find_body_parent (TNY_MIME_PART (msg));
636 fact = modest_runtime_get_platform_factory ();
637 if (parent != NULL) {
638 result = tny_platform_factory_new_mime_part (fact);
639 tny_mime_part_add_part (TNY_MIME_PART (parent), result);
640 g_object_unref (parent);
642 result = g_object_ref (msg);