1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Authors: Jeffrey Stedfast <fejj@ximian.com>
5 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
30 #include "camel-mime-filter-tohtml.h"
31 #include "camel-url-scanner.h"
32 #include "camel-utf8.h"
34 #define CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE(obj) \
35 (G_TYPE_INSTANCE_GET_PRIVATE \
36 ((obj), CAMEL_TYPE_MIME_FILTER_TOHTML, CamelMimeFilterToHTMLPrivate))
38 struct _CamelMimeFilterToHTMLPrivate {
40 CamelUrlScanner *scanner;
50 * TODO: convert common text/plain 'markup' to html. eg.:
52 * _word_ -> <u>_word_</u>
53 * *word* -> <b>*word*</b>
54 * /word/ -> <i>/word/</i>
59 #define FOOLISHLY_UNMUNGE_FROM 0
61 #define CONVERT_WEB_URLS CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS
62 #define CONVERT_ADDRSPEC CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES
68 { CONVERT_WEB_URLS, { "file://", "", camel_url_file_start, camel_url_file_end } },
69 { CONVERT_WEB_URLS, { "ftp://", "", camel_url_web_start, camel_url_web_end } },
70 { CONVERT_WEB_URLS, { "sftp://", "", camel_url_web_start, camel_url_web_end } },
71 { CONVERT_WEB_URLS, { "http://", "", camel_url_web_start, camel_url_web_end } },
72 { CONVERT_WEB_URLS, { "https://", "", camel_url_web_start, camel_url_web_end } },
73 { CONVERT_WEB_URLS, { "news://", "", camel_url_web_start, camel_url_web_end } },
74 { CONVERT_WEB_URLS, { "nntp://", "", camel_url_web_start, camel_url_web_end } },
75 { CONVERT_WEB_URLS, { "telnet://", "", camel_url_web_start, camel_url_web_end } },
76 { CONVERT_WEB_URLS, { "webcal://", "", camel_url_web_start, camel_url_web_end } },
77 { CONVERT_WEB_URLS, { "mailto:", "", camel_url_web_start, camel_url_web_end } },
78 { CONVERT_WEB_URLS, { "callto:", "", camel_url_web_start, camel_url_web_end } },
79 { CONVERT_WEB_URLS, { "h323:", "", camel_url_web_start, camel_url_web_end } },
80 { CONVERT_WEB_URLS, { "sip:", "", camel_url_web_start, camel_url_web_end } },
81 { CONVERT_WEB_URLS, { "www.", "http://", camel_url_web_start, camel_url_web_end } },
82 { CONVERT_WEB_URLS, { "ftp.", "ftp://", camel_url_web_start, camel_url_web_end } },
83 { CONVERT_ADDRSPEC, { "@", "mailto:", camel_url_addrspec_start, camel_url_addrspec_end } },
86 G_DEFINE_TYPE (CamelMimeFilterToHTML, camel_mime_filter_tohtml, CAMEL_TYPE_MIME_FILTER)
89 check_size (CamelMimeFilter *mime_filter,
96 if (*outend - outptr >= len)
99 offset = outptr - mime_filter->outbuf;
101 camel_mime_filter_set_size (
102 mime_filter, mime_filter->outsize + len, TRUE);
104 *outend = mime_filter->outbuf + mime_filter->outsize;
106 return mime_filter->outbuf + offset;
110 append_string_verbatim (CamelMimeFilter *mime_filter,
115 gsize len = strlen (str);
117 outptr = check_size (mime_filter, outptr, outend, len);
118 memcpy (outptr, str, len);
125 citation_depth (const gchar *in,
128 register const gchar *inptr = in;
134 #if FOOLISHLY_UNMUNGE_FROM
135 /* check that it isn't an escaped From line */
136 if (!strncmp (inptr, "From", 4))
140 while (inptr < inend && *inptr != '\n') {
144 if (inptr >= inend || *inptr++ != '>')
154 writeln (CamelMimeFilter *mime_filter,
160 CamelMimeFilterToHTMLPrivate *priv;
161 const guchar *inptr = in;
163 priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (mime_filter);
165 while (inptr < inend) {
168 outptr = check_size (mime_filter, outptr, outend, 16);
170 u = camel_utf8_getc_limit (&inptr, inend);
173 g_warning ("Truncated UTF-8 buffer (The cause might be missing character encoding information in the message header. Try a different character encoding.)");
176 outptr = g_stpcpy (outptr, "<");
180 outptr = g_stpcpy (outptr, ">");
184 outptr = g_stpcpy (outptr, "&");
188 outptr = g_stpcpy (outptr, """);
192 if (priv->flags & (CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES)) {
194 outptr = check_size (mime_filter, outptr, outend, 7);
195 outptr = g_stpcpy (outptr, " ");
197 } while (priv->column % 8);
200 /* otherwise, FALL THROUGH */
202 if (priv->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES
203 && ((inptr == (in + 1) || (inptr < inend && (*inptr == ' ' || *inptr == '\t'))))) {
204 outptr = g_stpcpy (outptr, " ");
208 /* otherwise, FALL THROUGH */
210 if (u >= 20 && u <0x80)
213 if (priv->flags & CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT)
216 outptr += sprintf (outptr, "&#%u;", u);
227 html_convert (CamelMimeFilter *mime_filter,
236 CamelMimeFilterToHTMLPrivate *priv;
238 gchar *outptr, *outend;
243 priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (mime_filter);
246 if (priv->pre_open) {
247 /* close the pre-tag */
248 outend = mime_filter->outbuf + mime_filter->outsize;
249 outptr = check_size (mime_filter, mime_filter->outbuf, &outend, 10);
250 outptr = g_stpcpy (outptr, "</pre>");
251 priv->pre_open = FALSE;
253 *out = mime_filter->outbuf;
254 *outlen = outptr - mime_filter->outbuf;
255 *outprespace = mime_filter->outpre;
265 camel_mime_filter_set_size (mime_filter, inlen * 2 + 6, FALSE);
269 outptr = mime_filter->outbuf;
270 outend = mime_filter->outbuf + mime_filter->outsize;
272 if (priv->flags & CAMEL_MIME_FILTER_TOHTML_PRE && !priv->pre_open) {
273 outptr = g_stpcpy (outptr, "<pre>");
274 priv->pre_open = TRUE;
279 while (inptr < inend && *inptr != '\n')
282 if (inptr >= inend && !flush)
288 if (priv->flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) {
289 if ((depth = citation_depth (start, inend)) > 0) {
290 /* FIXME: we could easily support multiple color depths here */
292 outptr = check_size (mime_filter, outptr, &outend, 25);
293 outptr += sprintf (outptr, "<font color=\"#%06x\">", (priv->color & 0xffffff));
295 #if FOOLISHLY_UNMUNGE_FROM
296 else if (*start == '>') {
301 } else if (priv->flags & CAMEL_MIME_FILTER_TOHTML_CITE) {
302 outptr = check_size (mime_filter, outptr, &outend, 6);
303 outptr = g_stpcpy (outptr, "> ");
307 #define CONVERT_URLS (CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES)
308 if (priv->flags & CONVERT_URLS) {
315 if (camel_url_scanner_scan (priv->scanner, start, len - (len > 0 && start[len - 1] == 0 ? 1 : 0), &match)) {
316 /* write out anything before the first regex match */
319 (const guchar *) start,
320 (const guchar *) start +
324 start += match.um_so;
327 matchlen = match.um_eo - match.um_so;
329 /* write out the href tag */
330 outptr = append_string_verbatim (mime_filter, "<a href=\"", outptr, &outend);
331 /* prefix shouldn't need escaping, but let's be safe */
334 (const guchar *) match.prefix,
335 (const guchar *) match.prefix +
336 strlen (match.prefix),
340 (const guchar *) start,
341 (const guchar *) start +
344 outptr = append_string_verbatim (
348 /* now write the matched string */
351 (const guchar *) start,
352 (const guchar *) start +
355 priv->column += matchlen;
359 /* close the href tag */
360 outptr = append_string_verbatim (
364 /* nothing matched so write out the remainder of this line buffer */
367 (const guchar *) start,
368 (const guchar *) start + len,
376 (const guchar *) start,
377 (const guchar *) inptr,
381 if ((priv->flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) && depth > 0) {
382 outptr = check_size (mime_filter, outptr, &outend, 8);
383 outptr = g_stpcpy (outptr, "</font>");
387 if (priv->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_NL) {
388 outptr = check_size (mime_filter, outptr, &outend, 5);
389 outptr = g_stpcpy (outptr, "<br>");
396 } while (inptr < inend);
399 /* flush the rest of our input buffer */
401 outptr = writeln (mime_filter, (const guchar *) start, (const guchar *) inend, outptr, &outend);
403 if (priv->pre_open) {
404 /* close the pre-tag */
405 outptr = check_size (mime_filter, outptr, &outend, 10);
406 outptr = g_stpcpy (outptr, "</pre>");
408 } else if (start < inend) {
410 camel_mime_filter_backup (mime_filter, start, (unsigned) (inend - start));
413 *out = mime_filter->outbuf;
414 *outlen = outptr - mime_filter->outbuf;
415 *outprespace = mime_filter->outpre;
419 mime_filter_tohtml_finalize (GObject *object)
421 CamelMimeFilterToHTMLPrivate *priv;
423 priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (object);
425 camel_url_scanner_free (priv->scanner);
427 /* Chain up to parent's finalize() method. */
428 G_OBJECT_CLASS (camel_mime_filter_tohtml_parent_class)->finalize (object);
432 mime_filter_tohtml_filter (CamelMimeFilter *mime_filter,
441 mime_filter, in, len, prespace,
442 out, outlen, outprespace, FALSE);
446 mime_filter_tohtml_complete (CamelMimeFilter *mime_filter,
455 mime_filter, in, len, prespace,
456 out, outlen, outprespace, TRUE);
460 mime_filter_tohtml_reset (CamelMimeFilter *mime_filter)
462 CamelMimeFilterToHTMLPrivate *priv;
464 priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (mime_filter);
467 priv->pre_open = FALSE;
471 camel_mime_filter_tohtml_class_init (CamelMimeFilterToHTMLClass *class)
473 GObjectClass *object_class;
474 CamelMimeFilterClass *filter_class;
476 g_type_class_add_private (class, sizeof (CamelMimeFilterToHTMLPrivate));
478 object_class = G_OBJECT_CLASS (class);
479 object_class->finalize = mime_filter_tohtml_finalize;
481 filter_class = CAMEL_MIME_FILTER_CLASS (class);
482 filter_class->filter = mime_filter_tohtml_filter;
483 filter_class->complete = mime_filter_tohtml_complete;
484 filter_class->reset = mime_filter_tohtml_reset;
488 camel_mime_filter_tohtml_init (CamelMimeFilterToHTML *filter)
490 filter->priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (filter);
491 filter->priv->scanner = camel_url_scanner_new ();
495 * camel_mime_filter_tohtml_new:
496 * @flags: bitwise flags defining the behaviour
497 * @color: color to use when highlighting quoted text
499 * Create a new #CamelMimeFilterToHTML object to convert plain text
502 * Returns: a new #CamelMimeFilterToHTML object
505 camel_mime_filter_tohtml_new (guint32 flags,
508 CamelMimeFilter *filter;
509 CamelMimeFilterToHTMLPrivate *priv;
512 filter = g_object_new (CAMEL_TYPE_MIME_FILTER_TOHTML, NULL);
513 priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (filter);
518 for (i = 0; i < G_N_ELEMENTS (patterns); i++) {
519 if (patterns[i].mask & flags)
520 camel_url_scanner_add (
521 priv->scanner, &patterns[i].pattern);
528 * camel_text_to_html:
530 * @flags: bitwise flags defining the html conversion behaviour
531 * @color: color to use when syntax highlighting
533 * Convert @in from plain text into HTML.
535 * Returns: a newly allocated string containing the HTMLified version
539 camel_text_to_html (const gchar *in,
543 CamelMimeFilter *filter;
544 gsize outlen, outpre;
547 g_return_val_if_fail (in != NULL, NULL);
549 filter = camel_mime_filter_tohtml_new (flags, color);
551 camel_mime_filter_complete (
552 filter, (gchar *) in, strlen (in), 0,
553 &outbuf, &outlen, &outpre);
555 outbuf = g_strndup (outbuf, outlen);
557 g_object_unref (filter);