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"
35 * TODO: convert common text/plain 'markup' to html. eg.:
37 * _word_ -> <u>_word_</u>
38 * *word* -> <b>*word*</b>
39 * /word/ -> <i>/word/</i>
44 #define FOOLISHLY_UNMUNGE_FROM 0
46 #define CONVERT_WEB_URLS CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS
47 #define CONVERT_ADDRSPEC CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES
53 { CONVERT_WEB_URLS, { "file://", "", camel_url_file_start, camel_url_file_end } },
54 { CONVERT_WEB_URLS, { "ftp://", "", camel_url_web_start, camel_url_web_end } },
55 { CONVERT_WEB_URLS, { "sftp://", "", camel_url_web_start, camel_url_web_end } },
56 { CONVERT_WEB_URLS, { "http://", "", camel_url_web_start, camel_url_web_end } },
57 { CONVERT_WEB_URLS, { "https://", "", camel_url_web_start, camel_url_web_end } },
58 { CONVERT_WEB_URLS, { "news://", "", camel_url_web_start, camel_url_web_end } },
59 { CONVERT_WEB_URLS, { "nntp://", "", camel_url_web_start, camel_url_web_end } },
60 { CONVERT_WEB_URLS, { "telnet://", "", camel_url_web_start, camel_url_web_end } },
61 { CONVERT_WEB_URLS, { "webcal://", "", camel_url_web_start, camel_url_web_end } },
62 { CONVERT_WEB_URLS, { "mailto:", "", camel_url_web_start, camel_url_web_end } },
63 { CONVERT_WEB_URLS, { "callto:", "", camel_url_web_start, camel_url_web_end } },
64 { CONVERT_WEB_URLS, { "h323:", "", camel_url_web_start, camel_url_web_end } },
65 { CONVERT_WEB_URLS, { "sip:", "", camel_url_web_start, camel_url_web_end } },
66 { CONVERT_WEB_URLS, { "www.", "http://", camel_url_web_start, camel_url_web_end } },
67 { CONVERT_WEB_URLS, { "ftp.", "ftp://", camel_url_web_start, camel_url_web_end } },
68 { CONVERT_ADDRSPEC, { "@", "mailto:", camel_url_addrspec_start, camel_url_addrspec_end } },
71 static void camel_mime_filter_tohtml_class_init (CamelMimeFilterToHTMLClass *klass);
72 static void camel_mime_filter_tohtml_init (CamelMimeFilterToHTML *filter);
73 static void camel_mime_filter_tohtml_finalize (CamelObject *obj);
75 static CamelMimeFilterClass *camel_mime_filter_tohtml_parent;
78 camel_mime_filter_tohtml_get_type (void)
80 static CamelType type = CAMEL_INVALID_TYPE;
82 if (type == CAMEL_INVALID_TYPE) {
83 type = camel_type_register (camel_mime_filter_get_type (),
84 "CamelMimeFilterToHTML",
85 sizeof (CamelMimeFilterToHTML),
86 sizeof (CamelMimeFilterToHTMLClass),
87 (CamelObjectClassInitFunc) camel_mime_filter_tohtml_class_init,
89 (CamelObjectInitFunc) camel_mime_filter_tohtml_init,
90 (CamelObjectFinalizeFunc) camel_mime_filter_tohtml_finalize);
97 camel_mime_filter_tohtml_finalize (CamelObject *obj)
99 CamelMimeFilterToHTML *filter = (CamelMimeFilterToHTML *) obj;
101 camel_url_scanner_free (filter->scanner);
105 camel_mime_filter_tohtml_init (CamelMimeFilterToHTML *filter)
107 filter->scanner = camel_url_scanner_new ();
112 filter->pre_open = FALSE;
116 check_size (CamelMimeFilter *filter, gchar *outptr, gchar **outend, gsize len)
120 if (*outend - outptr >= len)
123 offset = outptr - filter->outbuf;
125 camel_mime_filter_set_size (filter, filter->outsize + len, TRUE);
127 *outend = filter->outbuf + filter->outsize;
129 return filter->outbuf + offset;
133 append_string_verbatim (CamelMimeFilter *filter, const gchar *str, gchar *outptr, gchar **outend)
135 gsize len = strlen (str);
137 outptr = check_size (filter, outptr, outend, len);
138 memcpy(outptr, str, len);
145 citation_depth (const gchar *in)
147 register const gchar *inptr = in;
153 #if FOOLISHLY_UNMUNGE_FROM
154 /* check that it isn't an escaped From line */
155 if (!strncmp (inptr, "From", 4))
159 while (*inptr != '\n') {
173 writeln (CamelMimeFilter *filter, const guchar *in, const guchar *inend, gchar *outptr, gchar **outend)
175 CamelMimeFilterToHTML *html = (CamelMimeFilterToHTML *) filter;
176 const guchar *inptr = in;
178 while (inptr < inend) {
181 outptr = check_size (filter, outptr, outend, 16);
183 u = camel_utf8_getc_limit (&inptr, inend);
186 g_warning("Truncated utf8 buffer");
189 outptr = g_stpcpy (outptr, "<");
193 outptr = g_stpcpy (outptr, ">");
197 outptr = g_stpcpy (outptr, "&");
201 outptr = g_stpcpy (outptr, """);
205 if (html->flags & (CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES)) {
207 outptr = check_size (filter, outptr, outend, 7);
208 outptr = g_stpcpy (outptr, " ");
210 } while (html->column % 8);
213 /* otherwise, FALL THROUGH */
215 if (html->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES
216 && ((inptr == (in + 1) || (inptr < inend && (*inptr == ' ' || *inptr == '\t'))))) {
217 outptr = g_stpcpy (outptr, " ");
221 /* otherwise, FALL THROUGH */
223 if (u >= 20 && u <0x80)
226 if (html->flags & CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT)
229 outptr += sprintf(outptr, "&#%u;", u);
240 html_convert (CamelMimeFilter *filter, const gchar *in, gsize inlen, gsize prespace,
241 gchar **out, gsize *outlen, gsize *outprespace, gboolean flush)
243 CamelMimeFilterToHTML *html = (CamelMimeFilterToHTML *) filter;
245 gchar *outptr, *outend;
251 if (html->pre_open) {
252 /* close the pre-tag */
253 outend = filter->outbuf + filter->outsize;
254 outptr = check_size (filter, filter->outbuf, &outend, 10);
255 outptr = g_stpcpy (outptr, "</pre>");
256 html->pre_open = FALSE;
258 *out = filter->outbuf;
259 *outlen = outptr - filter->outbuf;
260 *outprespace = filter->outpre;
270 camel_mime_filter_set_size (filter, inlen * 2 + 6, FALSE);
274 outptr = filter->outbuf;
275 outend = filter->outbuf + filter->outsize;
277 if (html->flags & CAMEL_MIME_FILTER_TOHTML_PRE && !html->pre_open) {
278 outptr = g_stpcpy (outptr, "<pre>");
279 html->pre_open = TRUE;
284 while (inptr < inend && *inptr != '\n')
287 if (inptr >= inend && !flush)
293 if (html->flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) {
294 if ((depth = citation_depth (start)) > 0) {
295 /* FIXME: we could easily support multiple color depths here */
297 outptr = check_size (filter, outptr, &outend, 25);
298 outptr += sprintf(outptr, "<font color=\"#%06x\">", (html->color & 0xffffff));
300 #if FOOLISHLY_UNMUNGE_FROM
301 else if (*start == '>') {
306 } else if (html->flags & CAMEL_MIME_FILTER_TOHTML_CITE) {
307 outptr = check_size (filter, outptr, &outend, 6);
308 outptr = g_stpcpy (outptr, "> ");
312 #define CONVERT_URLS (CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES)
313 if (html->flags & CONVERT_URLS) {
320 if (camel_url_scanner_scan (html->scanner, start, len, &match)) {
321 /* write out anything before the first regex match */
322 outptr = writeln (filter, (const guchar *)start, (const guchar *)start + match.um_so,
325 start += match.um_so;
328 matchlen = match.um_eo - match.um_so;
330 /* write out the href tag */
331 outptr = append_string_verbatim (filter, "<a href=\"", outptr, &outend);
332 /* prefix shouldn't need escaping, but let's be safe */
333 outptr = writeln (filter,
334 (const guchar *)match.prefix,
335 (const guchar *)match.prefix + strlen (match.prefix),
337 outptr = writeln (filter,
338 (const guchar *)start,
339 (const guchar *)start + matchlen,
341 outptr = append_string_verbatim (filter, "\">", outptr, &outend);
343 /* now write the matched string */
344 outptr = writeln (filter,
345 (const guchar *)start,
346 (const guchar *)start + matchlen,
348 html->column += matchlen;
352 /* close the href tag */
353 outptr = append_string_verbatim (filter, "</a>", outptr, &outend);
355 /* nothing matched so write out the remainder of this line buffer */
356 outptr = writeln (filter, (const guchar *)start, (const guchar *)start + len, outptr, &outend);
361 outptr = writeln (filter, (const guchar *)start, (const guchar *)inptr, outptr, &outend);
364 if ((html->flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) && depth > 0) {
365 outptr = check_size (filter, outptr, &outend, 8);
366 outptr = g_stpcpy (outptr, "</font>");
370 if (html->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_NL) {
371 outptr = check_size (filter, outptr, &outend, 5);
372 outptr = g_stpcpy (outptr, "<br>");
379 } while (inptr < inend);
382 /* flush the rest of our input buffer */
384 outptr = writeln (filter, (const guchar *)start, (const guchar *)inend, outptr, &outend);
386 if (html->pre_open) {
387 /* close the pre-tag */
388 outptr = check_size (filter, outptr, &outend, 10);
389 outptr = g_stpcpy (outptr, "</pre>");
391 } else if (start < inend) {
393 camel_mime_filter_backup (filter, start, (unsigned) (inend - start));
396 *out = filter->outbuf;
397 *outlen = outptr - filter->outbuf;
398 *outprespace = filter->outpre;
402 filter_filter (CamelMimeFilter *filter, const gchar *in, gsize len, gsize prespace,
403 gchar **out, gsize *outlen, gsize *outprespace)
405 html_convert (filter, in, len, prespace, out, outlen, outprespace, FALSE);
409 filter_complete (CamelMimeFilter *filter, const gchar *in, gsize len, gsize prespace,
410 gchar **out, gsize *outlen, gsize *outprespace)
412 html_convert (filter, in, len, prespace, out, outlen, outprespace, TRUE);
416 filter_reset (CamelMimeFilter *filter)
418 CamelMimeFilterToHTML *html = (CamelMimeFilterToHTML *) filter;
421 html->pre_open = FALSE;
425 camel_mime_filter_tohtml_class_init (CamelMimeFilterToHTMLClass *klass)
427 CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass;
429 camel_mime_filter_tohtml_parent = CAMEL_MIME_FILTER_CLASS (camel_type_get_global_classfuncs (camel_mime_filter_get_type ()));
431 filter_class->reset = filter_reset;
432 filter_class->filter = filter_filter;
433 filter_class->complete = filter_complete;
437 * camel_mime_filter_tohtml_new:
438 * @flags: bitwise flags defining the behaviour
439 * @color: color to use when highlighting quoted text
441 * Create a new #CamelMimeFilterToHTML object to convert plain text
444 * Returns: a new #CamelMimeFilterToHTML object
447 camel_mime_filter_tohtml_new (guint32 flags, guint32 color)
449 CamelMimeFilterToHTML *new;
452 new = CAMEL_MIME_FILTER_TOHTML (camel_object_new (camel_mime_filter_tohtml_get_type ()));
457 for (i = 0; i < G_N_ELEMENTS (patterns); i++) {
458 if (patterns[i].mask & flags)
459 camel_url_scanner_add (new->scanner, &patterns[i].pattern);
462 return CAMEL_MIME_FILTER (new);
466 * camel_text_to_html:
468 * @flags: bitwise flags defining the html conversion behaviour
469 * @color: color to use when syntax highlighting
471 * Convert @in from plain text into HTML.
473 * Returns: a newly allocated string containing the HTMLified version
477 camel_text_to_html (const gchar *in, guint32 flags, guint32 color)
479 CamelMimeFilter *filter;
480 gsize outlen, outpre;
483 g_return_val_if_fail (in != NULL, NULL);
485 filter = camel_mime_filter_tohtml_new (flags, color);
487 camel_mime_filter_complete (filter, (gchar *) in, strlen (in), 0,
488 &outbuf, &outlen, &outpre);
490 outbuf = g_strndup (outbuf, outlen);
492 camel_object_unref (filter);