/* * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Jeffrey Stedfast * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "em-inline-filter.h" #include "em-stripsig-filter.h" #include "em-format-quote.h" #define EM_FORMAT_QUOTE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), EM_TYPE_FORMAT_QUOTE, EMFormatQuotePrivate)) struct _EMFormatQuotePrivate { gchar *credits; EMFormatQuoteFlags flags; guint32 text_html_flags; }; static void emfq_builtin_init (EMFormatQuoteClass *efhc); static CamelMimePart * decode_inline_parts (CamelMimePart *part, GCancellable *cancellable); static void emfq_parse_text_plain (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void emfq_parse_text_enriched (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void emfq_parse_text_html (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void emfq_parse_attachment (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void emfq_write_text_plain (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void emfq_write_text_enriched (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void emfq_write_text_html (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static gpointer parent_class; /* Decodes inline encoded parts of 'part'. The returned pointer, * if not NULL, should be unreffed with g_object_unref(). */ static CamelMimePart * decode_inline_parts (CamelMimePart *part, GCancellable *cancellable) { CamelMultipart *mp; CamelStream *null; CamelStream *filtered_stream; EMInlineFilter *inline_filter; g_return_val_if_fail (part != NULL, NULL); null = camel_stream_null_new (); filtered_stream = camel_stream_filter_new (null); g_object_unref (null); inline_filter = em_inline_filter_new ( camel_mime_part_get_encoding (part), camel_mime_part_get_content_type (part)); camel_stream_filter_add ( CAMEL_STREAM_FILTER (filtered_stream), CAMEL_MIME_FILTER (inline_filter)); camel_data_wrapper_decode_to_stream_sync ( camel_medium_get_content (CAMEL_MEDIUM (part)), filtered_stream, cancellable, NULL); camel_stream_close (filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); if (!em_inline_filter_found_any (inline_filter)) { g_object_unref (inline_filter); return NULL; } mp = em_inline_filter_get_multipart (inline_filter); g_object_unref (inline_filter); if (mp) { part = camel_mime_part_new (); camel_medium_set_content ( CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (mp)); g_object_unref (mp); } else { g_object_ref (part); } return part; } static void emfq_format_text_header (EMFormatQuote *emfq, GString *buffer, const gchar *label, const gchar *value, guint32 flags, gint is_html) { const gchar *html; gchar *mhtml = NULL; if (value == NULL) return; while (*value == ' ') value++; if (!is_html) html = mhtml = camel_text_to_html (value, 0, 0); else html = value; if (flags & EM_FORMAT_HEADER_BOLD) g_string_append_printf ( buffer, "%s: %s
", label, html); else g_string_append_printf ( buffer, "%s: %s
", label, html); g_free (mhtml); } static const gchar *addrspec_hdrs[] = { "Sender", "From", "Reply-To", "To", "Cc", "Bcc", "Resent-Sender", "Resent-from", "Resent-Reply-To", "Resent-To", "Resent-cc", "Resent-Bcc", NULL }; #if 0 /* FIXME: include Sender and Resent-* headers too? */ /* For Translators only: The following strings are * used in the header table in the preview pane. */ static gchar *i18n_hdrs[] = { N_("From"), N_("Reply-To"), N_("To"), N_("Cc"), N_("Bcc") }; #endif static void emfq_format_address (GString *out, struct _camel_header_address *a) { guint32 flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES; gchar *name, *mailto, *addr; while (a) { if (a->name) name = camel_text_to_html (a->name, flags, 0); else name = NULL; switch (a->type) { case CAMEL_HEADER_ADDRESS_NAME: if (name && *name) { gchar *real, *mailaddr; g_string_append_printf (out, "%s <", name); /* rfc2368 for mailto syntax and url encoding extras */ if ((real = camel_header_encode_phrase ((guchar *) a->name))) { mailaddr = g_strdup_printf ("%s <%s>", real, a->v.addr); g_free (real); mailto = camel_url_encode (mailaddr, "?=&()"); g_free (mailaddr); } else { mailto = camel_url_encode (a->v.addr, "?=&()"); } } else { mailto = camel_url_encode (a->v.addr, "?=&()"); } addr = camel_text_to_html (a->v.addr, flags, 0); g_string_append_printf ( out, "%s", mailto, addr); g_free (mailto); g_free (addr); if (name && *name) g_string_append (out, ">"); break; case CAMEL_HEADER_ADDRESS_GROUP: g_string_append_printf (out, "%s: ", name); emfq_format_address (out, a->v.members); g_string_append_printf (out, ";"); break; default: g_warning ("Invalid address type"); break; } g_free (name); a = a->next; if (a) g_string_append (out, ", "); } } static void canon_header_name (gchar *name) { gchar *inptr = name; /* canonicalise the header name... first letter is * capitalised and any letter following a '-' also gets * capitalised */ if (g_ascii_islower (*inptr)) *inptr = g_ascii_toupper (*inptr); inptr++; while (*inptr) { if (inptr[-1] == '-' && g_ascii_islower (*inptr)) *inptr = g_ascii_toupper (*inptr); else if (g_ascii_isupper (*inptr)) *inptr = g_ascii_tolower (*inptr); inptr++; } } static void emfq_format_header (EMFormat *emf, GString *buffer, CamelMedium *part, const gchar *namein, guint32 flags, const gchar *charset) { CamelMimeMessage *msg = (CamelMimeMessage *) part; EMFormatQuote *emfq = (EMFormatQuote *) emf; gchar *name, *buf, *value = NULL; const gchar *txt, *label; gboolean addrspec = FALSE; gint is_html = FALSE; gint i; name = g_alloca (strlen (namein) + 1); strcpy (name, namein); canon_header_name (name); /* Never quote Bcc headers */ if (g_str_equal (name, "Bcc") || g_str_equal (name, "Resent-Bcc")) return; for (i = 0; addrspec_hdrs[i]; i++) { if (!strcmp (name, addrspec_hdrs[i])) { addrspec = TRUE; break; } } label = _(name); if (addrspec) { struct _camel_header_address *addrs; GString *html; if (!(txt = camel_medium_get_header (part, name))) return; buf = camel_header_unfold (txt); addrs = camel_header_address_decode ( txt, em_format_get_charset (emf) ? em_format_get_charset (emf) : em_format_get_default_charset (emf)); if (addrs == NULL) { g_free (buf); return; } g_free (buf); html = g_string_new (""); emfq_format_address (html, addrs); camel_header_address_unref (addrs); txt = value = html->str; g_string_free (html, FALSE); flags |= EM_FORMAT_HEADER_BOLD; is_html = TRUE; } else if (!strcmp (name, "Subject")) { txt = camel_mime_message_get_subject (msg); label = _("Subject"); flags |= EM_FORMAT_HEADER_BOLD; } else if (!strcmp (name, "X-Evolution-Mailer")) { /* pseudo-header */ if (!(txt = camel_medium_get_header (part, "x-mailer"))) if (!(txt = camel_medium_get_header (part, "user-agent"))) if (!(txt = camel_medium_get_header (part, "x-newsreader"))) if (!(txt = camel_medium_get_header (part, "x-mimeole"))) return; txt = value = camel_header_format_ctext (txt, charset); label = _("Mailer"); flags |= EM_FORMAT_HEADER_BOLD; } else if (!strcmp (name, "Date") || !strcmp (name, "Resent-Date")) { if (!(txt = camel_medium_get_header (part, name))) return; flags |= EM_FORMAT_HEADER_BOLD; } else { txt = camel_medium_get_header (part, name); buf = camel_header_unfold (txt); txt = value = camel_header_decode_string (txt, charset); g_free (buf); } emfq_format_text_header (emfq, buffer, label, txt, flags, is_html); g_free (value); } static void emfq_format_headers (EMFormatQuote *emfq, GString *buffer, CamelMedium *part) { EMFormat *emf = (EMFormat *) emfq; CamelContentType *ct; const gchar *charset; GList *link; if (!part) return; ct = camel_mime_part_get_content_type ((CamelMimePart *) part); charset = camel_content_type_param (ct, "charset"); charset = camel_iconv_charset_name (charset); /* dump selected headers */ link = g_queue_peek_head_link (&emf->header_list); while (link != NULL) { EMFormatHeader *h = link->data; emfq_format_header ( emf, buffer, part, h->name, h->flags, charset); link = g_list_next (link); } g_string_append (buffer, "
\n"); } static void emfq_dispose (GObject *object) { /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (parent_class)->dispose (object); } static void emfq_finalize (GObject *object) { EMFormatQuotePrivate *priv; priv = EM_FORMAT_QUOTE_GET_PRIVATE (object); g_free (priv->credits); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (parent_class)->finalize (object); } /******************************************************************************/ static void emfq_parse_text_plain (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; CamelMimePart *mp; gint len; len = part_id->len; g_string_append (part_id, ".text_plain"); mp = decode_inline_parts (part, cancellable); if (mp) { if (CAMEL_IS_MULTIPART (camel_medium_get_content (CAMEL_MEDIUM (mp)))) { em_format_parse_part (emf, mp, part_id, info, cancellable); } g_object_unref (mp); } puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); puri->write_func = emfq_write_text_plain; puri->mime_type = g_strdup ("text/html"); em_format_add_puri (emf, puri); g_string_truncate (part_id, len); } static void emfq_parse_text_html (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; gint len; len = part_id->len; g_string_append (part_id, ".text_html"); puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); puri->write_func = emfq_write_text_html; puri->mime_type = g_strdup ("text/html"); em_format_add_puri (emf, puri); g_string_truncate (part_id, len); } static void emfq_parse_text_enriched (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; gint len; len = part_id->len; g_string_append (part_id, ".text_enriched"); puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); puri->write_func = emfq_write_text_enriched; puri->mime_type = g_strdup ("text/html"); em_format_add_puri (emf, puri); g_string_truncate (part_id, len); } static void emfq_parse_attachment (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; gint len; len = part_id->len; g_string_append (part_id, ".attachment"); puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); puri->write_func = emfq_write_text_html; puri->mime_type = g_strdup ("text/html"); puri->is_attachment = TRUE; em_format_add_puri (emf, puri); g_string_truncate (part_id, len); } /******************************************************************************/ static void emfq_write_attachment (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { EMFormatQuote *emfq = EM_FORMAT_QUOTE (emf); const EMFormatHandler *handler; gchar *text, *html; CamelContentType *ct; const gchar *mime_type; ct = camel_mime_part_get_content_type (puri->part); if (ct) { mime_type = camel_content_type_simple (ct); camel_content_type_unref (ct); } else { mime_type = "application/octet-stream"; } handler = em_format_find_handler (emf, mime_type); if (!em_format_is_inline (emf, puri->uri, puri->part, handler)) return; camel_stream_write_string ( stream, "" "
\n", cancellable, NULL); /* output some info about it */ text = em_format_describe_part (puri->part, mime_type); html = camel_text_to_html ( text, emfq->priv->text_html_flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); camel_stream_write_string (stream, html, cancellable, NULL); g_free (html); g_free (text); camel_stream_write_string ( stream, "
", cancellable, NULL); if (handler && handler->write_func) handler->write_func (emf, puri, stream, info, cancellable); } static void emfq_base_init (EMFormatQuoteClass *klass) { emfq_builtin_init (klass); } static void emfq_class_init (EMFormatQuoteClass *klass) { GObjectClass *object_class; parent_class = g_type_class_peek_parent (klass); g_type_class_add_private (klass, sizeof (EMFormatQuotePrivate)); object_class = G_OBJECT_CLASS (klass); object_class->dispose = emfq_dispose; object_class->finalize = emfq_finalize; } static void emfq_init (EMFormatQuote *emfq) { emfq->priv = EM_FORMAT_QUOTE_GET_PRIVATE (emfq); /* we want to convert url's etc */ emfq->priv->text_html_flags = CAMEL_MIME_FILTER_TOHTML_PRE | CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; } GType em_format_quote_get_type (void) { static GType type = 0; if (G_UNLIKELY (type == 0)) { static const GTypeInfo type_info = { sizeof (EMFormatQuoteClass), (GBaseInitFunc) emfq_base_init, (GBaseFinalizeFunc) NULL, (GClassInitFunc) emfq_class_init, (GClassFinalizeFunc) NULL, NULL, /* class_data */ sizeof (EMFormatQuote), 0, /* n_preallocs */ (GInstanceInitFunc) emfq_init, NULL /* value_table */ }; type = g_type_register_static ( EM_TYPE_FORMAT, "EMFormatQuote", &type_info, 0); } return type; } EMFormatQuote * em_format_quote_new (CamelSession *session, const gchar *credits, CamelStream *stream, EMFormatQuoteFlags flags) { EMFormatQuote *emfq; g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL); g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL); /* Steam must also be seekable so we can reset its position. */ g_return_val_if_fail (G_IS_SEEKABLE (stream), NULL); emfq = g_object_new ( EM_TYPE_FORMAT_QUOTE, "session", session, NULL); emfq->priv->credits = g_strdup (credits); emfq->priv->flags = flags; return emfq; } void em_format_quote_write (EMFormatQuote *emfq, CamelStream *stream, GCancellable *cancellable) { EMFormat *emf; GSettings *settings; GList *iter; EMFormatWriterInfo info = { 0 }; emf = (EMFormat *) emfq; g_seekable_seek ( G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL); settings = g_settings_new ("org.gnome.evolution.mail"); if (g_settings_get_boolean ( settings, "composer-top-signature")) camel_stream_write_string ( stream, "
\n", cancellable, NULL); g_object_unref (settings); if (emfq->priv->credits && *emfq->priv->credits) { gchar *credits = g_strdup_printf ("%s
", emfq->priv->credits); camel_stream_write_string (stream, credits, cancellable, NULL); g_free (credits); } else { camel_stream_write_string (stream, "
", cancellable, NULL); } if (emfq->priv->flags & EM_FORMAT_QUOTE_CITE) camel_stream_write_string (stream, "\n" "
\n", cancellable, NULL); for (iter = emf->mail_part_list; iter; iter = iter->next) { EMFormatPURI *puri = iter->data; if (puri->is_attachment || !puri->write_func) continue; puri = iter->data; if (emfq->priv->flags & EM_FORMAT_QUOTE_HEADERS) { GString *buffer = g_string_new (""); emfq_format_headers (emfq, buffer, (CamelMedium *) puri->part); camel_stream_write_string (stream, buffer->str, cancellable, NULL); g_string_free (buffer, TRUE); } puri->write_func (emf, puri, stream, &info, cancellable); } if (emfq->priv->flags & EM_FORMAT_QUOTE_CITE) camel_stream_write_string ( stream, "
", cancellable, NULL); } static void emfq_write_text_plain (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { EMFormatQuote *emfq = EM_FORMAT_QUOTE (emf); CamelStream *filtered_stream; CamelMimeFilter *html_filter; CamelMimeFilter *sig_strip; CamelContentType *type; const gchar *format; guint32 rgb = 0x737373, flags; if (!puri->part) return; flags = emfq->priv->text_html_flags; /* Check for RFC 2646 flowed text. */ type = camel_mime_part_get_content_type (puri->part); if (camel_content_type_is(type, "text", "plain") && (format = camel_content_type_param(type, "format")) && !g_ascii_strcasecmp(format, "flowed")) flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED; filtered_stream = camel_stream_filter_new (stream); if ((emfq->priv->flags & EM_FORMAT_QUOTE_KEEP_SIG) == 0) { sig_strip = em_stripsig_filter_new (TRUE); camel_stream_filter_add ( CAMEL_STREAM_FILTER (filtered_stream), sig_strip); g_object_unref (sig_strip); } html_filter = camel_mime_filter_tohtml_new (flags, rgb); camel_stream_filter_add ( CAMEL_STREAM_FILTER (filtered_stream), html_filter); g_object_unref (html_filter); em_format_format_text ( EM_FORMAT (emfq), filtered_stream, CAMEL_DATA_WRAPPER (puri->part), cancellable); camel_stream_flush (filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); } static void emfq_write_text_enriched (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { CamelStream *filtered_stream; CamelMimeFilter *enriched; guint32 flags = 0; CamelContentType *ct; const gchar *mime_type = NULL; ct = camel_mime_part_get_content_type (puri->part); if (ct) { mime_type = camel_content_type_simple (ct); camel_content_type_unref (ct); } if (g_strcmp0 (mime_type, "text/richtext") == 0) { flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT; camel_stream_write_string ( stream, "\n\n", cancellable, NULL); } else { camel_stream_write_string ( stream, "\n\n", cancellable, NULL); } enriched = camel_mime_filter_enriched_new (flags); filtered_stream = camel_stream_filter_new (stream); camel_stream_filter_add ( CAMEL_STREAM_FILTER (filtered_stream), enriched); g_object_unref (enriched); camel_stream_write_string (stream, "


", cancellable, NULL); em_format_format_text ( emf, filtered_stream, CAMEL_DATA_WRAPPER (puri->part), cancellable); camel_stream_flush (filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); } static void emfq_write_text_html (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { EMFormatQuotePrivate *priv; priv = EM_FORMAT_QUOTE_GET_PRIVATE (emf); camel_stream_write_string ( stream, "\n\n", cancellable, NULL); if ((priv->flags & EM_FORMAT_QUOTE_KEEP_SIG) == 0) { CamelMimeFilter *sig_strip; CamelStream *filtered_stream; filtered_stream = camel_stream_filter_new (stream); sig_strip = em_stripsig_filter_new (FALSE); camel_stream_filter_add ( CAMEL_STREAM_FILTER (filtered_stream), sig_strip); g_object_unref (sig_strip); em_format_format_text ( emf, filtered_stream, (CamelDataWrapper *) puri->part, cancellable); camel_stream_flush (filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); } else { em_format_format_text ( emf, stream, (CamelDataWrapper *) puri->part, cancellable); } } /****************************************************************************/ static EMFormatHandler type_builtin_table[] = { { (gchar *) "text/plain", emfq_parse_text_plain, emfq_write_text_plain, }, { (gchar *) "text/enriched", emfq_parse_text_enriched, emfq_write_text_enriched, }, { (gchar *) "text/richtext", emfq_parse_text_enriched, emfq_write_text_enriched, }, { (gchar *) "text/html", emfq_parse_text_html, emfq_write_text_html, }, { (gchar *) "text/*", emfq_parse_text_plain, emfq_write_text_plain, }, { (gchar *) "message/external-body", em_format_empty_parser, em_format_empty_writer, }, { (gchar *) "multipart/appledouble", em_format_empty_parser, em_format_empty_writer, }, /* internal evolution types */ { (gchar *) "x-evolution/evolution-rss-feed", 0, emfq_write_text_html, }, { (gchar *) "x-evolution/message/attachment", emfq_parse_attachment, emfq_write_attachment, }, }; static void emfq_builtin_init (EMFormatQuoteClass *efhc) { gint ii; EMFormatClass *emfc = (EMFormatClass *) efhc; for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) em_format_class_add_handler ( emfc, &type_builtin_table[ii]); }