Coding style and whitespace cleanup.
[platform/upstream/evolution-data-server.git] / camel / camel-mime-filter-enriched.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *
5  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6  *
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.
11  *
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.
16  *
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.
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <stdio.h>
28 #include <string.h>
29
30 #include "camel-mime-filter-enriched.h"
31 #include "camel-string-utils.h"
32
33 #define CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE(obj) \
34         (G_TYPE_INSTANCE_GET_PRIVATE \
35         ((obj), CAMEL_TYPE_MIME_FILTER_ENRICHED, CamelMimeFilterEnrichedPrivate))
36
37 struct _CamelMimeFilterEnrichedPrivate {
38         guint32 flags;
39         gint nofill;
40 };
41
42 /* text/enriched is rfc1896 */
43
44 typedef gchar * (*EnrichedParamParser) (const gchar *inptr, gint inlen);
45
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);
49
50 static struct {
51         const gchar *enriched;
52         const gchar *html;
53         gboolean needs_param;
54         EnrichedParamParser parse_param; /* parses *and * validates the input */
55 } enriched_tags[] = {
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               },
88
89         /* don't handle this tag yet... */
90         { "paraindent",  "<!-- ",               /* TRUE */ FALSE, NULL    },
91         { "/paraindent", " -->",                FALSE, NULL               },
92
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               },
98 };
99
100 static GHashTable *enriched_hash = NULL;
101
102 G_DEFINE_TYPE (CamelMimeFilterEnriched, camel_mime_filter_enriched, CAMEL_TYPE_MIME_FILTER)
103
104 #if 0
105 static gboolean
106 enriched_tag_needs_param (const gchar *tag)
107 {
108         gint i;
109
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;
113
114         return FALSE;
115 }
116 #endif
117
118 static gboolean
119 html_tag_needs_param (const gchar *tag)
120 {
121         return strstr (tag, "%s") != NULL;
122 }
123
124 static const gchar *valid_colors[] = {
125         "red", "green", "blue", "yellow", "cyan", "magenta", "black", "white"
126 };
127
128 static gchar *
129 param_parse_color (const gchar *inptr,
130                    gint inlen)
131 {
132         const gchar *inend, *end;
133         guint32 rgb = 0;
134         guint v;
135         gint i;
136
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]);
140         }
141
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] */
147                 end = inptr;
148                 inend = inptr + inlen;
149                 while (end < inend && ((*end >= 'a' && *end <= 'z') || (*end >= 'A' && *end <= 'Z')))
150                         end++;
151
152                 return g_strndup (inptr, end - inptr);
153         }
154
155         for (i = 0; i < 3; i++) {
156                 v = strtoul (inptr, (gchar **) &end, 16);
157                 if (end != inptr + 4)
158                         goto invalid_format;
159
160                 v >>= 8;
161                 rgb = (rgb << 8) | (v & 0xff);
162
163                 inptr += 5;
164         }
165
166         return g_strdup_printf ("#%.6X", rgb);
167
168  invalid_format:
169
170         /* default color? */
171         return g_strdup ("black");
172 }
173
174 static gchar *
175 param_parse_font (const gchar *fontfamily,
176                   gint inlen)
177 {
178         register const gchar *inptr = fontfamily;
179         const gchar *inend = inptr + inlen;
180
181         /* don't allow any of '"', '<', nor '>' */
182         while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
183                 inptr++;
184
185         return g_strndup (fontfamily, inptr - fontfamily);
186 }
187
188 static gchar *
189 param_parse_lang (const gchar *lang,
190                   gint inlen)
191 {
192         register const gchar *inptr = lang;
193         const gchar *inend = inptr + inlen;
194
195         /* don't allow any of '"', '<', nor '>' */
196         while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
197                 inptr++;
198
199         return g_strndup (lang, inptr - lang);
200 }
201
202 static gchar *
203 param_parse (const gchar *enriched,
204              const gchar *inptr,
205              gint inlen)
206 {
207         gint i;
208
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);
212         }
213
214         g_assert_not_reached ();
215
216         return NULL;
217 }
218
219 #define IS_RICHTEXT CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT
220
221 static void
222 enriched_to_html (CamelMimeFilter *mime_filter,
223                   const gchar *in,
224                   gsize inlen,
225                   gsize prespace,
226                   gchar **out,
227                   gsize *outlen,
228                   gsize *outprespace,
229                   gboolean flush)
230 {
231         CamelMimeFilterEnrichedPrivate *priv;
232         const gchar *tag, *inend, *outend;
233         register const gchar *inptr;
234         register gchar *outptr;
235
236         priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (mime_filter);
237
238         camel_mime_filter_set_size (mime_filter, inlen * 2 + 6, FALSE);
239
240         inptr = in;
241         inend = in + inlen;
242         outptr = mime_filter->outbuf;
243         outend = mime_filter->outbuf + mime_filter->outsize;
244
245  retry:
246         do {
247                 while (inptr < inend && outptr < outend && !strchr (" <>&\n", *inptr))
248                         *outptr++ = *inptr++;
249
250                 if (outptr == outend)
251                         goto backup;
252
253                 if ((inptr + 1) >= inend)
254                         break;
255
256                 switch (*inptr++) {
257                 case ' ':
258                         while (inptr < inend && (outptr + 7) < outend && *inptr == ' ') {
259                                 memcpy (outptr, "&nbsp;", 6);
260                                 outptr += 6;
261                                 inptr++;
262                         }
263
264                         if (outptr < outend)
265                                 *outptr++ = ' ';
266
267                         break;
268                 case '\n':
269                         if (!(priv->flags & IS_RICHTEXT)) {
270                                 /* text/enriched */
271                                 if (priv->nofill > 0) {
272                                         if ((outptr + 4) < outend) {
273                                                 memcpy (outptr, "<br>", 4);
274                                                 outptr += 4;
275                                         } else {
276                                                 inptr--;
277                                                 goto backup;
278                                         }
279                                 } else if (*inptr == '\n') {
280                                         if ((outptr + 4) >= outend) {
281                                                 inptr--;
282                                                 goto backup;
283                                         }
284
285                                         while (inptr < inend && (outptr + 4) < outend && *inptr == '\n') {
286                                                 memcpy (outptr, "<br>", 4);
287                                                 outptr += 4;
288                                                 inptr++;
289                                         }
290                                 } else {
291                                         *outptr++ = ' ';
292                                 }
293                         } else {
294                                 /* text/richtext */
295                                 *outptr++ = ' ';
296                         }
297                         break;
298                 case '>':
299                         if ((outptr + 4) < outend) {
300                                 memcpy (outptr, "&gt;", 4);
301                                 outptr += 4;
302                         } else {
303                                 inptr--;
304                                 goto backup;
305                         }
306                         break;
307                 case '&':
308                         if ((outptr + 5) < outend) {
309                                 memcpy (outptr, "&amp;", 5);
310                                 outptr += 5;
311                         } else {
312                                 inptr--;
313                                 goto backup;
314                         }
315                         break;
316                 case '<':
317                         if (!(priv->flags & IS_RICHTEXT)) {
318                                 /* text/enriched */
319                                 if (*inptr == '<') {
320                                         if ((outptr + 4) < outend) {
321                                                 memcpy (outptr, "&lt;", 4);
322                                                 outptr += 4;
323                                                 inptr++;
324                                                 break;
325                                         } else {
326                                                 inptr--;
327                                                 goto backup;
328                                         }
329                                 }
330                         } else {
331                                 /* text/richtext */
332                                 if ((inend - inptr) >= 3 && (outptr + 4) < outend) {
333                                         if (strncmp (inptr, "lt>", 3) == 0) {
334                                                 memcpy (outptr, "&lt;", 4);
335                                                 outptr += 4;
336                                                 inptr += 3;
337                                                 break;
338                                         } else if (strncmp (inptr, "nl>", 3) == 0) {
339                                                 memcpy (outptr, "<br>", 4);
340                                                 outptr += 4;
341                                                 inptr += 3;
342                                                 break;
343                                         }
344                                 } else {
345                                         inptr--;
346                                         goto backup;
347                                 }
348                         }
349
350                         tag = inptr;
351                         while (inptr < inend && *inptr != '>')
352                                 inptr++;
353
354                         if (inptr == inend) {
355                                 inptr = tag - 1;
356                                 goto need_input;
357                         }
358
359                         if (!g_ascii_strncasecmp (tag, "nofill>", 7)) {
360                                 if ((outptr + 5) < outend) {
361                                         priv->nofill++;
362                                 } else {
363                                         inptr = tag - 1;
364                                         goto backup;
365                                 }
366                         } else if (!g_ascii_strncasecmp (tag, "/nofill>", 8)) {
367                                 if ((outptr + 6) < outend) {
368                                         priv->nofill--;
369                                 } else {
370                                         inptr = tag - 1;
371                                         goto backup;
372                                 }
373                         } else {
374                                 const gchar *html_tag;
375                                 gchar *enriched_tag;
376                                 gint len;
377
378                                 len = inptr - tag;
379                                 enriched_tag = g_alloca (len + 1);
380                                 memcpy (enriched_tag, tag, len);
381                                 enriched_tag[len] = '\0';
382
383                                 html_tag = g_hash_table_lookup (enriched_hash, enriched_tag);
384
385                                 if (html_tag) {
386                                         if (html_tag_needs_param (html_tag)) {
387                                                 const gchar *start;
388                                                 gchar *param;
389
390                                                 while (inptr < inend && *inptr != '<')
391                                                         inptr++;
392
393                                                 if (inptr == inend || (inend - inptr) <= 15) {
394                                                         inptr = tag - 1;
395                                                         goto need_input;
396                                                 }
397
398                                                 if (g_ascii_strncasecmp (inptr, "<param>", 7) != 0) {
399                                                         /* ignore the enriched command tag... */
400                                                         inptr -= 1;
401                                                         goto loop;
402                                                 }
403
404                                                 inptr += 7;
405                                                 start = inptr;
406
407                                                 while (inptr < inend && *inptr != '<')
408                                                         inptr++;
409
410                                                 if (inptr == inend || (inend - inptr) <= 8) {
411                                                         inptr = tag - 1;
412                                                         goto need_input;
413                                                 }
414
415                                                 if (g_ascii_strncasecmp (inptr, "</param>", 8) != 0) {
416                                                         /* ignore the enriched command tag... */
417                                                         inptr += 7;
418                                                         goto loop;
419                                                 }
420
421                                                 len = inptr - start;
422                                                 param = param_parse (enriched_tag, start, len);
423                                                 len = strlen (param);
424
425                                                 inptr += 7;
426
427                                                 len += strlen (html_tag);
428
429                                                 if ((outptr + len) < outend) {
430                                                         outptr += snprintf (outptr, len, html_tag, param);
431                                                         g_free (param);
432                                                 } else {
433                                                         g_free (param);
434                                                         inptr = tag - 1;
435                                                         goto backup;
436                                                 }
437                                         } else {
438                                                 len = strlen (html_tag);
439                                                 if ((outptr + len) < outend) {
440                                                         memcpy (outptr, html_tag, len);
441                                                         outptr += len;
442                                                 } else {
443                                                         inptr = tag - 1;
444                                                         goto backup;
445                                                 }
446                                         }
447                                 }
448                         }
449
450                 loop:
451                         inptr++;
452                         break;
453                 default:
454                         break;
455                 }
456         } while (inptr < inend);
457
458  need_input:
459
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
462          * do. */
463
464         if (inptr < inend)
465                 camel_mime_filter_backup (mime_filter, inptr, (unsigned) (inend - inptr));
466
467         *out = mime_filter->outbuf;
468         *outlen = outptr - mime_filter->outbuf;
469         *outprespace = mime_filter->outpre;
470
471         return;
472
473  backup:
474
475         if (flush) {
476                 gsize offset, grow;
477
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;
483
484                 goto retry;
485         } else {
486                 camel_mime_filter_backup (mime_filter, inptr, (unsigned) (inend - inptr));
487         }
488
489         *out = mime_filter->outbuf;
490         *outlen = outptr - mime_filter->outbuf;
491         *outprespace = mime_filter->outpre;
492 }
493
494 static void
495 mime_filter_enriched_filter (CamelMimeFilter *mime_filter,
496                              const gchar *in,
497                              gsize len,
498                              gsize prespace,
499                              gchar **out,
500                              gsize *outlen,
501                              gsize *outprespace)
502 {
503         enriched_to_html (
504                 mime_filter, in, len, prespace,
505                 out, outlen, outprespace, FALSE);
506 }
507
508 static void
509 mime_filter_enriched_complete (CamelMimeFilter *mime_filter,
510                                const gchar *in,
511                                gsize len,
512                                gsize prespace,
513                                gchar **out,
514                                gsize *outlen,
515                                gsize *outprespace)
516 {
517         enriched_to_html (
518                 mime_filter, in, len, prespace,
519                 out, outlen, outprespace, TRUE);
520 }
521
522 static void
523 mime_filter_enriched_reset (CamelMimeFilter *mime_filter)
524 {
525         CamelMimeFilterEnrichedPrivate *priv;
526
527         priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (mime_filter);
528
529         priv->nofill = 0;
530 }
531
532 static void
533 camel_mime_filter_enriched_class_init (CamelMimeFilterEnrichedClass *class)
534 {
535         CamelMimeFilterClass *mime_filter_class;
536         gint i;
537
538         g_type_class_add_private (class, sizeof (CamelMimeFilterEnrichedPrivate));
539
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;
544
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 (
549                         enriched_hash,
550                         (gpointer) enriched_tags[i].enriched,
551                         (gpointer) enriched_tags[i].html);
552 }
553
554 static void
555 camel_mime_filter_enriched_init (CamelMimeFilterEnriched *filter)
556 {
557         filter->priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (filter);
558 }
559
560 /**
561  * camel_mime_filter_enriched_new:
562  * @flags: bitwise set of flags to specify filter behaviour
563  *
564  * Create a new #CamelMimeFilterEnriched object to convert input text
565  * streams from text/plain into text/enriched or text/richtext.
566  *
567  * Returns: a new #CamelMimeFilterEnriched object
568  **/
569 CamelMimeFilter *
570 camel_mime_filter_enriched_new (guint32 flags)
571 {
572         CamelMimeFilter *new;
573         CamelMimeFilterEnrichedPrivate *priv;
574
575         new = g_object_new (CAMEL_TYPE_MIME_FILTER_ENRICHED, NULL);
576         priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (new);
577
578         priv->flags = flags;
579
580         return new;
581 }
582
583 /**
584  * camel_enriched_to_html:
585  * @in: input textual string
586  * @flags: flags specifying filter behaviour
587  *
588  * Convert @in from text/plain into text/enriched or text/richtext
589  * based on @flags.
590  *
591  * Returns: a newly allocated string containing the enriched or
592  * richtext version of @in.
593  **/
594 gchar *
595 camel_enriched_to_html (const gchar *in,
596                         guint32 flags)
597 {
598         CamelMimeFilter *filter;
599         gsize outlen, outpre;
600         gchar *outbuf;
601
602         if (in == NULL)
603                 return NULL;
604
605         filter = camel_mime_filter_enriched_new (flags);
606
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);
610
611         return outbuf;
612 }