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.
31 #include "camel-mime-filter-enriched.h"
32 #include "camel-string-utils.h"
34 /* text/enriched is rfc1896 */
36 typedef char * (*EnrichedParamParser) (const char *inptr, int inlen);
38 static char *param_parse_colour (const char *inptr, int inlen);
39 static char *param_parse_font (const char *inptr, int inlen);
40 static char *param_parse_lang (const char *inptr, int inlen);
46 EnrichedParamParser parse_param; /* parses *and* validates the input */
48 { "bold", "<b>", FALSE, NULL },
49 { "/bold", "</b>", FALSE, NULL },
50 { "italic", "<i>", FALSE, NULL },
51 { "/italic", "</i>", FALSE, NULL },
52 { "fixed", "<tt>", FALSE, NULL },
53 { "/fixed", "</tt>", FALSE, NULL },
54 { "smaller", "<font size=-1>", FALSE, NULL },
55 { "/smaller", "</font>", FALSE, NULL },
56 { "bigger", "<font size=+1>", FALSE, NULL },
57 { "/bigger", "</font>", FALSE, NULL },
58 { "underline", "<u>", FALSE, NULL },
59 { "/underline", "</u>", FALSE, NULL },
60 { "center", "<p align=center>", FALSE, NULL },
61 { "/center", "</p>", FALSE, NULL },
62 { "flushleft", "<p align=left>", FALSE, NULL },
63 { "/flushleft", "</p>", FALSE, NULL },
64 { "flushright", "<p align=right>", FALSE, NULL },
65 { "/flushright", "</p>", FALSE, NULL },
66 { "excerpt", "<blockquote>", FALSE, NULL },
67 { "/excerpt", "</blockquote>", FALSE, NULL },
68 { "paragraph", "<p>", FALSE, NULL },
69 { "signature", "<address>", FALSE, NULL },
70 { "/signature", "</address>", FALSE, NULL },
71 { "comment", "<!-- ", FALSE, NULL },
72 { "/comment", " -->", FALSE, NULL },
73 { "np", "<hr>", FALSE, NULL },
74 { "fontfamily", "<font face=\"%s\">", TRUE, param_parse_font },
75 { "/fontfamily", "</font>", FALSE, NULL },
76 { "color", "<font color=\"%s\">", TRUE, param_parse_colour },
77 { "/color", "</font>", FALSE, NULL },
78 { "lang", "<span lang=\"%s\">", TRUE, param_parse_lang },
79 { "/lang", "</span>", FALSE, NULL },
81 /* don't handle this tag yet... */
82 { "paraindent", "<!-- ", /* TRUE */ FALSE, NULL },
83 { "/paraindent", " -->", FALSE, NULL },
85 /* as soon as we support all the tags that can have a param
86 * tag argument, these should be unnecessary, but we'll keep
87 * them anyway just in case? */
88 { "param", "<!-- ", FALSE, NULL },
89 { "/param", " -->", FALSE, NULL },
92 #define NUM_ENRICHED_TAGS (sizeof (enriched_tags) / sizeof (enriched_tags[0]))
94 static GHashTable *enriched_hash = NULL;
97 static void camel_mime_filter_enriched_class_init (CamelMimeFilterEnrichedClass *klass);
98 static void camel_mime_filter_enriched_init (CamelMimeFilterEnriched *filter);
99 static void camel_mime_filter_enriched_finalize (CamelObject *obj);
101 static void filter_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
102 char **out, size_t *outlen, size_t *outprespace);
103 static void filter_complete (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
104 char **out, size_t *outlen, size_t *outprespace);
105 static void filter_reset (CamelMimeFilter *filter);
108 static CamelMimeFilterClass *parent_class = NULL;
112 camel_mime_filter_enriched_get_type (void)
114 static CamelType type = CAMEL_INVALID_TYPE;
116 if (type == CAMEL_INVALID_TYPE) {
117 type = camel_type_register (camel_mime_filter_get_type (),
118 "CamelMimeFilterEnriched",
119 sizeof (CamelMimeFilterEnriched),
120 sizeof (CamelMimeFilterEnrichedClass),
121 (CamelObjectClassInitFunc) camel_mime_filter_enriched_class_init,
123 (CamelObjectInitFunc) camel_mime_filter_enriched_init,
124 (CamelObjectFinalizeFunc) camel_mime_filter_enriched_finalize);
131 camel_mime_filter_enriched_class_init (CamelMimeFilterEnrichedClass *klass)
133 CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass;
136 parent_class = CAMEL_MIME_FILTER_CLASS (camel_mime_filter_get_type ());
138 filter_class->reset = filter_reset;
139 filter_class->filter = filter_filter;
140 filter_class->complete = filter_complete;
142 if (!enriched_hash) {
143 enriched_hash = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
144 for (i = 0; i < NUM_ENRICHED_TAGS; i++)
145 g_hash_table_insert (enriched_hash, enriched_tags[i].enriched,
146 enriched_tags[i].html);
151 camel_mime_filter_enriched_finalize (CamelObject *obj)
157 camel_mime_filter_enriched_init (CamelMimeFilterEnriched *filter)
166 enriched_tag_needs_param (const char *tag)
170 for (i = 0; i < NUM_ENRICHED_TAGS; i++)
171 if (!g_ascii_strcasecmp (tag, enriched_tags[i].enriched))
172 return enriched_tags[i].needs_param;
179 html_tag_needs_param (const char *tag)
181 return strstr (tag, "%s") != NULL;
184 static const char *valid_colours[] = {
185 "red", "green", "blue", "yellow", "cyan", "magenta", "black", "white"
188 #define NUM_VALID_COLOURS (sizeof (valid_colours) / sizeof (valid_colours[0]))
191 param_parse_colour (const char *inptr, int inlen)
193 const char *inend, *end;
198 for (i = 0; i < NUM_VALID_COLOURS; i++) {
199 if (!g_ascii_strncasecmp (inptr, valid_colours[i], inlen))
200 return g_strdup (valid_colours[i]);
203 /* check for numeric r/g/b in the format: ####,####,#### */
204 if (inptr[4] != ',' || inptr[9] != ',') {
205 /* okay, mailer must have used a string name that
206 * rfc1896 did not specify? do some simple scanning
207 * action, a colour name MUST be [a-zA-Z] */
209 inend = inptr + inlen;
210 while (end < inend && ((*end >= 'a' && *end <= 'z') || (*end >= 'A' && *end <= 'Z')))
213 return g_strndup (inptr, end - inptr);
216 for (i = 0; i < 3; i++) {
217 v = strtoul (inptr, (char **) &end, 16);
218 if (end != inptr + 4)
222 rgb = (rgb << 8) | (v & 0xff);
227 return g_strdup_printf ("#%.6X", rgb);
231 /* default colour? */
232 return g_strdup ("black");
236 param_parse_font (const char *fontfamily, int inlen)
238 register const char *inptr = fontfamily;
239 const char *inend = inptr + inlen;
241 /* don't allow any of '"', '<', nor '>' */
242 while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
245 return g_strndup (fontfamily, inptr - fontfamily);
249 param_parse_lang (const char *lang, int inlen)
251 register const char *inptr = lang;
252 const char *inend = inptr + inlen;
254 /* don't allow any of '"', '<', nor '>' */
255 while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
258 return g_strndup (lang, inptr - lang);
262 param_parse (const char *enriched, const char *inptr, int inlen)
266 for (i = 0; i < NUM_ENRICHED_TAGS; i++) {
267 if (!g_ascii_strcasecmp (enriched, enriched_tags[i].enriched))
268 return enriched_tags[i].parse_param (inptr, inlen);
271 g_assert_not_reached ();
276 #define IS_RICHTEXT CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT
279 enriched_to_html (CamelMimeFilter *filter, char *in, size_t inlen, size_t prespace,
280 char **out, size_t *outlen, size_t *outprespace, gboolean flush)
282 CamelMimeFilterEnriched *enriched = (CamelMimeFilterEnriched *) filter;
283 const char *tag, *inend, *outend;
284 register const char *inptr;
285 register char *outptr;
287 camel_mime_filter_set_size (filter, inlen * 2 + 6, FALSE);
291 outptr = filter->outbuf;
292 outend = filter->outbuf + filter->outsize;
296 while (inptr < inend && outptr < outend && !strchr (" <>&\n", *inptr))
297 *outptr++ = *inptr++;
299 if (outptr == outend)
302 if ((inptr + 1) >= inend)
307 while (inptr < inend && (outptr + 7) < outend && *inptr == ' ') {
308 memcpy (outptr, " ", 6);
318 if (!(enriched->flags & IS_RICHTEXT)) {
320 if (enriched->nofill > 0) {
321 if ((outptr + 4) < outend) {
322 memcpy (outptr, "<br>", 4);
328 } else if (*inptr == '\n') {
329 if ((outptr + 4) >= outend) {
334 while (inptr < inend && (outptr + 4) < outend && *inptr == '\n') {
335 memcpy (outptr, "<br>", 4);
348 if ((outptr + 4) < outend) {
349 memcpy (outptr, ">", 4);
357 if ((outptr + 5) < outend) {
358 memcpy (outptr, "&", 5);
366 if (!(enriched->flags & IS_RICHTEXT)) {
369 if ((outptr + 4) < outend) {
370 memcpy (outptr, "<", 4);
381 if ((inend - inptr) >= 3 && (outptr + 4) < outend) {
382 if (strncmp (inptr, "lt>", 3) == 0) {
383 memcpy (outptr, "<", 4);
387 } else if (strncmp (inptr, "nl>", 3) == 0) {
388 memcpy (outptr, "<br>", 4);
400 while (inptr < inend && *inptr != '>')
403 if (inptr == inend) {
408 if (!g_ascii_strncasecmp (tag, "nofill>", 7)) {
409 if ((outptr + 5) < outend) {
415 } else if (!g_ascii_strncasecmp (tag, "/nofill>", 8)) {
416 if ((outptr + 6) < outend) {
423 const char *html_tag;
428 enriched_tag = g_alloca (len + 1);
429 memcpy (enriched_tag, tag, len);
430 enriched_tag[len] = '\0';
432 html_tag = g_hash_table_lookup (enriched_hash, enriched_tag);
435 if (html_tag_needs_param (html_tag)) {
439 while (inptr < inend && *inptr != '<')
442 if (inptr == inend || (inend - inptr) <= 15) {
447 if (g_ascii_strncasecmp (inptr, "<param>", 7) != 0) {
448 /* ignore the enriched command tag... */
456 while (inptr < inend && *inptr != '<')
459 if (inptr == inend || (inend - inptr) <= 8) {
464 if (g_ascii_strncasecmp (inptr, "</param>", 8) != 0) {
465 /* ignore the enriched command tag... */
471 param = param_parse (enriched_tag, start, len);
472 len = strlen (param);
476 len += strlen (html_tag);
478 if ((outptr + len) < outend) {
479 outptr += snprintf (outptr, len, html_tag, param);
487 len = strlen (html_tag);
488 if ((outptr + len) < outend) {
489 memcpy (outptr, html_tag, len);
505 } while (inptr < inend);
509 /* the reason we ignore @flush here is because if there isn't
510 enough input to parse a tag, then there's nothing we can
514 camel_mime_filter_backup (filter, inptr, (unsigned) (inend - inptr));
516 *out = filter->outbuf;
517 *outlen = outptr - filter->outbuf;
518 *outprespace = filter->outpre;
527 grow = (inend - inptr) * 2 + 20;
528 offset = outptr - filter->outbuf;
529 camel_mime_filter_set_size (filter, filter->outsize + grow, TRUE);
530 outend = filter->outbuf + filter->outsize;
531 outptr = filter->outbuf + offset;
535 camel_mime_filter_backup (filter, inptr, (unsigned) (inend - inptr));
538 *out = filter->outbuf;
539 *outlen = outptr - filter->outbuf;
540 *outprespace = filter->outpre;
544 filter_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
545 char **out, size_t *outlen, size_t *outprespace)
547 enriched_to_html (filter, in, len, prespace, out, outlen, outprespace, FALSE);
551 filter_complete (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
552 char **out, size_t *outlen, size_t *outprespace)
554 enriched_to_html (filter, in, len, prespace, out, outlen, outprespace, TRUE);
558 filter_reset (CamelMimeFilter *filter)
560 CamelMimeFilterEnriched *enriched = (CamelMimeFilterEnriched *) filter;
562 enriched->nofill = 0;
567 * camel_mime_filter_enriched_new:
568 * @flags: bitwise set of flags to specify filter behaviour
570 * Create a new #CamelMimeFilterEnriched object to convert input text
571 * streams from text/plain into text/enriched or text/richtext.
573 * Returns a new #CamelMimeFilterEnriched object
576 camel_mime_filter_enriched_new (guint32 flags)
578 CamelMimeFilterEnriched *new;
580 new = (CamelMimeFilterEnriched *) camel_object_new (CAMEL_TYPE_MIME_FILTER_ENRICHED);
583 return CAMEL_MIME_FILTER (new);
588 * camel_enriched_to_html:
589 * @in: input textual string
590 * @flags: flags specifying filter behaviour
592 * Convert @in from text/plain into text/enriched or text/richtext
595 * Returns a newly allocated string containing the enriched or
596 * richtext version of @in.
599 camel_enriched_to_html(const char *in, guint32 flags)
601 CamelMimeFilter *filter;
602 size_t outlen, outpre;
608 filter = camel_mime_filter_enriched_new(flags);
610 camel_mime_filter_complete(filter, (char *)in, strlen(in), 0, &outbuf, &outlen, &outpre);
611 outbuf = g_strndup (outbuf, outlen);
612 camel_object_unref (filter);