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-enriched.h"
31 #include "camel-string-utils.h"
33 #define CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE(obj) \
34 (G_TYPE_INSTANCE_GET_PRIVATE \
35 ((obj), CAMEL_TYPE_MIME_FILTER_ENRICHED, CamelMimeFilterEnrichedPrivate))
37 struct _CamelMimeFilterEnrichedPrivate {
42 /* text/enriched is rfc1896 */
44 typedef gchar * (*EnrichedParamParser) (const gchar *inptr, gint inlen);
46 static gchar *param_parse_color (const gchar *inptr, gint inlen);
47 static gchar *param_parse_font (const gchar *inptr, gint inlen);
48 static gchar *param_parse_lang (const gchar *inptr, gint inlen);
51 const gchar *enriched;
54 EnrichedParamParser parse_param; /* parses *and * validates the input */
56 { "bold", "<b>", FALSE, NULL },
57 { "/bold", "</b>", FALSE, NULL },
58 { "italic", "<i>", FALSE, NULL },
59 { "/italic", "</i>", FALSE, NULL },
60 { "fixed", "<tt>", FALSE, NULL },
61 { "/fixed", "</tt>", FALSE, NULL },
62 { "smaller", "<font size=-1>", FALSE, NULL },
63 { "/smaller", "</font>", FALSE, NULL },
64 { "bigger", "<font size=+1>", FALSE, NULL },
65 { "/bigger", "</font>", FALSE, NULL },
66 { "underline", "<u>", FALSE, NULL },
67 { "/underline", "</u>", FALSE, NULL },
68 { "center", "<p align=center>", FALSE, NULL },
69 { "/center", "</p>", FALSE, NULL },
70 { "flushleft", "<p align=left>", FALSE, NULL },
71 { "/flushleft", "</p>", FALSE, NULL },
72 { "flushright", "<p align=right>", FALSE, NULL },
73 { "/flushright", "</p>", FALSE, NULL },
74 { "excerpt", "<blockquote>", FALSE, NULL },
75 { "/excerpt", "</blockquote>", FALSE, NULL },
76 { "paragraph", "<p>", FALSE, NULL },
77 { "signature", "<address>", FALSE, NULL },
78 { "/signature", "</address>", FALSE, NULL },
79 { "comment", "<!-- ", FALSE, NULL },
80 { "/comment", " -->", FALSE, NULL },
81 { "np", "<hr>", FALSE, NULL },
82 { "fontfamily", "<font face=\"%s\">", TRUE, param_parse_font },
83 { "/fontfamily", "</font>", FALSE, NULL },
84 { "color", "<font color=\"%s\">", TRUE, param_parse_color },
85 { "/color", "</font>", FALSE, NULL },
86 { "lang", "<span lang=\"%s\">", TRUE, param_parse_lang },
87 { "/lang", "</span>", FALSE, NULL },
89 /* don't handle this tag yet... */
90 { "paraindent", "<!-- ", /* TRUE */ FALSE, NULL },
91 { "/paraindent", " -->", FALSE, NULL },
93 /* as soon as we support all the tags that can have a param
94 * tag argument, these should be unnecessary, but we'll keep
95 * them anyway just in case? */
96 { "param", "<!-- ", FALSE, NULL },
97 { "/param", " -->", FALSE, NULL },
100 static GHashTable *enriched_hash = NULL;
102 G_DEFINE_TYPE (CamelMimeFilterEnriched, camel_mime_filter_enriched, CAMEL_TYPE_MIME_FILTER)
106 enriched_tag_needs_param (const gchar *tag)
110 for (i = 0; i < G_N_ELEMENTS (enriched_tags); i++)
111 if (!g_ascii_strcasecmp (tag, enriched_tags[i].enriched))
112 return enriched_tags[i].needs_param;
119 html_tag_needs_param (const gchar *tag)
121 return strstr (tag, "%s") != NULL;
124 static const gchar *valid_colors[] = {
125 "red", "green", "blue", "yellow", "cyan", "magenta", "black", "white"
129 param_parse_color (const gchar *inptr,
132 const gchar *inend, *end;
137 for (i = 0; i < G_N_ELEMENTS (valid_colors); i++) {
138 if (!g_ascii_strncasecmp (inptr, valid_colors[i], inlen))
139 return g_strdup (valid_colors[i]);
142 /* check for numeric r/g/b in the format: ####,####,#### */
143 if (inptr[4] != ',' || inptr[9] != ',') {
144 /* okay, mailer must have used a string name that
145 * rfc1896 did not specify? do some simple scanning
146 * action, a color name MUST be [a-zA-Z] */
148 inend = inptr + inlen;
149 while (end < inend && ((*end >= 'a' && *end <= 'z') || (*end >= 'A' && *end <= 'Z')))
152 return g_strndup (inptr, end - inptr);
155 for (i = 0; i < 3; i++) {
156 v = strtoul (inptr, (gchar **) &end, 16);
157 if (end != inptr + 4)
161 rgb = (rgb << 8) | (v & 0xff);
166 return g_strdup_printf ("#%.6X", rgb);
171 return g_strdup ("black");
175 param_parse_font (const gchar *fontfamily,
178 register const gchar *inptr = fontfamily;
179 const gchar *inend = inptr + inlen;
181 /* don't allow any of '"', '<', nor '>' */
182 while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
185 return g_strndup (fontfamily, inptr - fontfamily);
189 param_parse_lang (const gchar *lang,
192 register const gchar *inptr = lang;
193 const gchar *inend = inptr + inlen;
195 /* don't allow any of '"', '<', nor '>' */
196 while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
199 return g_strndup (lang, inptr - lang);
203 param_parse (const gchar *enriched,
209 for (i = 0; i < G_N_ELEMENTS (enriched_tags); i++) {
210 if (!g_ascii_strcasecmp (enriched, enriched_tags[i].enriched))
211 return enriched_tags[i].parse_param (inptr, inlen);
214 g_assert_not_reached ();
219 #define IS_RICHTEXT CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT
222 enriched_to_html (CamelMimeFilter *mime_filter,
231 CamelMimeFilterEnrichedPrivate *priv;
232 const gchar *tag, *inend, *outend;
233 register const gchar *inptr;
234 register gchar *outptr;
236 priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (mime_filter);
238 camel_mime_filter_set_size (mime_filter, inlen * 2 + 6, FALSE);
242 outptr = mime_filter->outbuf;
243 outend = mime_filter->outbuf + mime_filter->outsize;
247 while (inptr < inend && outptr < outend && !strchr (" <>&\n", *inptr))
248 *outptr++ = *inptr++;
250 if (outptr == outend)
253 if ((inptr + 1) >= inend)
258 while (inptr < inend && (outptr + 7) < outend && *inptr == ' ') {
259 memcpy (outptr, " ", 6);
269 if (!(priv->flags & IS_RICHTEXT)) {
271 if (priv->nofill > 0) {
272 if ((outptr + 4) < outend) {
273 memcpy (outptr, "<br>", 4);
279 } else if (*inptr == '\n') {
280 if ((outptr + 4) >= outend) {
285 while (inptr < inend && (outptr + 4) < outend && *inptr == '\n') {
286 memcpy (outptr, "<br>", 4);
299 if ((outptr + 4) < outend) {
300 memcpy (outptr, ">", 4);
308 if ((outptr + 5) < outend) {
309 memcpy (outptr, "&", 5);
317 if (!(priv->flags & IS_RICHTEXT)) {
320 if ((outptr + 4) < outend) {
321 memcpy (outptr, "<", 4);
332 if ((inend - inptr) >= 3 && (outptr + 4) < outend) {
333 if (strncmp (inptr, "lt>", 3) == 0) {
334 memcpy (outptr, "<", 4);
338 } else if (strncmp (inptr, "nl>", 3) == 0) {
339 memcpy (outptr, "<br>", 4);
351 while (inptr < inend && *inptr != '>')
354 if (inptr == inend) {
359 if (!g_ascii_strncasecmp (tag, "nofill>", 7)) {
360 if ((outptr + 5) < outend) {
366 } else if (!g_ascii_strncasecmp (tag, "/nofill>", 8)) {
367 if ((outptr + 6) < outend) {
374 const gchar *html_tag;
379 enriched_tag = g_alloca (len + 1);
380 memcpy (enriched_tag, tag, len);
381 enriched_tag[len] = '\0';
383 html_tag = g_hash_table_lookup (enriched_hash, enriched_tag);
386 if (html_tag_needs_param (html_tag)) {
390 while (inptr < inend && *inptr != '<')
393 if (inptr == inend || (inend - inptr) <= 15) {
398 if (g_ascii_strncasecmp (inptr, "<param>", 7) != 0) {
399 /* ignore the enriched command tag... */
407 while (inptr < inend && *inptr != '<')
410 if (inptr == inend || (inend - inptr) <= 8) {
415 if (g_ascii_strncasecmp (inptr, "</param>", 8) != 0) {
416 /* ignore the enriched command tag... */
422 param = param_parse (enriched_tag, start, len);
423 len = strlen (param);
427 len += strlen (html_tag);
429 if ((outptr + len) < outend) {
430 outptr += snprintf (outptr, len, html_tag, param);
438 len = strlen (html_tag);
439 if ((outptr + len) < outend) {
440 memcpy (outptr, html_tag, len);
456 } while (inptr < inend);
460 /* the reason we ignore @flush here is because if there isn't
461 * enough input to parse a tag, then there's nothing we can
465 camel_mime_filter_backup (mime_filter, inptr, (unsigned) (inend - inptr));
467 *out = mime_filter->outbuf;
468 *outlen = outptr - mime_filter->outbuf;
469 *outprespace = mime_filter->outpre;
478 grow = (inend - inptr) * 2 + 20;
479 offset = outptr - mime_filter->outbuf;
480 camel_mime_filter_set_size (mime_filter, mime_filter->outsize + grow, TRUE);
481 outend = mime_filter->outbuf + mime_filter->outsize;
482 outptr = mime_filter->outbuf + offset;
486 camel_mime_filter_backup (mime_filter, inptr, (unsigned) (inend - inptr));
489 *out = mime_filter->outbuf;
490 *outlen = outptr - mime_filter->outbuf;
491 *outprespace = mime_filter->outpre;
495 mime_filter_enriched_filter (CamelMimeFilter *mime_filter,
504 mime_filter, in, len, prespace,
505 out, outlen, outprespace, FALSE);
509 mime_filter_enriched_complete (CamelMimeFilter *mime_filter,
518 mime_filter, in, len, prespace,
519 out, outlen, outprespace, TRUE);
523 mime_filter_enriched_reset (CamelMimeFilter *mime_filter)
525 CamelMimeFilterEnrichedPrivate *priv;
527 priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (mime_filter);
533 camel_mime_filter_enriched_class_init (CamelMimeFilterEnrichedClass *class)
535 CamelMimeFilterClass *mime_filter_class;
538 g_type_class_add_private (class, sizeof (CamelMimeFilterEnrichedPrivate));
540 mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
541 mime_filter_class->filter = mime_filter_enriched_filter;
542 mime_filter_class->complete = mime_filter_enriched_complete;
543 mime_filter_class->reset = mime_filter_enriched_reset;
545 enriched_hash = g_hash_table_new (
546 camel_strcase_hash, camel_strcase_equal);
547 for (i = 0; i < G_N_ELEMENTS (enriched_tags); i++)
548 g_hash_table_insert (
550 (gpointer) enriched_tags[i].enriched,
551 (gpointer) enriched_tags[i].html);
555 camel_mime_filter_enriched_init (CamelMimeFilterEnriched *filter)
557 filter->priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (filter);
561 * camel_mime_filter_enriched_new:
562 * @flags: bitwise set of flags to specify filter behaviour
564 * Create a new #CamelMimeFilterEnriched object to convert input text
565 * streams from text/plain into text/enriched or text/richtext.
567 * Returns: a new #CamelMimeFilterEnriched object
570 camel_mime_filter_enriched_new (guint32 flags)
572 CamelMimeFilter *new;
573 CamelMimeFilterEnrichedPrivate *priv;
575 new = g_object_new (CAMEL_TYPE_MIME_FILTER_ENRICHED, NULL);
576 priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (new);
584 * camel_enriched_to_html:
585 * @in: input textual string
586 * @flags: flags specifying filter behaviour
588 * Convert @in from text/plain into text/enriched or text/richtext
591 * Returns: a newly allocated string containing the enriched or
592 * richtext version of @in.
595 camel_enriched_to_html (const gchar *in,
598 CamelMimeFilter *filter;
599 gsize outlen, outpre;
605 filter = camel_mime_filter_enriched_new (flags);
607 camel_mime_filter_complete (filter, (gchar *) in, strlen (in), 0, &outbuf, &outlen, &outpre);
608 outbuf = g_strndup (outbuf, outlen);
609 g_object_unref (filter);