Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[platform/upstream/evolution-data-server.git] / camel / camel-mime-filter-tohtml.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-tohtml.h"
31 #include "camel-url-scanner.h"
32 #include "camel-utf8.h"
33
34 #define CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE(obj) \
35         (G_TYPE_INSTANCE_GET_PRIVATE \
36         ((obj), CAMEL_TYPE_MIME_FILTER_TOHTML, CamelMimeFilterToHTMLPrivate))
37
38 struct _CamelMimeFilterToHTMLPrivate {
39
40         CamelUrlScanner *scanner;
41
42         guint32 flags;
43         guint32 color;
44
45         guint32 column   : 31;
46         guint32 pre_open : 1;
47 };
48
49 /**
50  * TODO: convert common text/plain 'markup' to html. eg.:
51  *
52  * _word_ -> <u>_word_</u>
53  * *word* -> <b>*word*</b>
54  * /word/ -> <i>/word/</i>
55  **/
56
57 #define d(x)
58
59 #define FOOLISHLY_UNMUNGE_FROM 0
60
61 #define CONVERT_WEB_URLS  CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS
62 #define CONVERT_ADDRSPEC  CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES
63
64 static struct {
65         guint mask;
66         urlpattern_t pattern;
67 } patterns[] = {
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 } },
84 };
85
86 G_DEFINE_TYPE (CamelMimeFilterToHTML, camel_mime_filter_tohtml, CAMEL_TYPE_MIME_FILTER)
87
88 static gchar *
89 check_size (CamelMimeFilter *mime_filter,
90             gchar *outptr,
91             gchar **outend,
92             gsize len)
93 {
94         gsize offset;
95
96         if (*outend - outptr >= len)
97                 return outptr;
98
99         offset = outptr - mime_filter->outbuf;
100
101         camel_mime_filter_set_size (
102                 mime_filter, mime_filter->outsize + len, TRUE);
103
104         *outend = mime_filter->outbuf + mime_filter->outsize;
105
106         return mime_filter->outbuf + offset;
107 }
108
109 static gchar *
110 append_string_verbatim (CamelMimeFilter *mime_filter,
111                         const gchar *str,
112                         gchar *outptr,
113                         gchar **outend)
114 {
115         gsize len = strlen (str);
116
117         outptr = check_size (mime_filter, outptr, outend, len);
118         memcpy (outptr, str, len);
119         outptr += len;
120
121         return outptr;
122 }
123
124 static gint
125 citation_depth (const gchar *in,
126                 const gchar *inend)
127 {
128         register const gchar *inptr = in;
129         gint depth = 1;
130
131         if (*inptr++ != '>')
132                 return 0;
133
134 #if FOOLISHLY_UNMUNGE_FROM
135         /* check that it isn't an escaped From line */
136         if (!strncmp (inptr, "From", 4))
137                 return 0;
138 #endif
139
140         while (inptr < inend && *inptr != '\n') {
141                 if (*inptr == ' ')
142                         inptr++;
143
144                 if (inptr >= inend || *inptr++ != '>')
145                         break;
146
147                 depth++;
148         }
149
150         return depth;
151 }
152
153 static gchar *
154 writeln (CamelMimeFilter *mime_filter,
155          const guchar *in,
156          const guchar *inend,
157          gchar *outptr,
158          gchar **outend)
159 {
160         CamelMimeFilterToHTMLPrivate *priv;
161         const guchar *inptr = in;
162
163         priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (mime_filter);
164
165         while (inptr < inend) {
166                 guint32 u;
167
168                 outptr = check_size (mime_filter, outptr, outend, 16);
169
170                 u = camel_utf8_getc_limit (&inptr, inend);
171                 switch (u) {
172                 case 0xffff:
173                         g_warning ("Truncated UTF-8 buffer (The cause might be missing character encoding information in the message header. Try a different character encoding.)");
174                         return outptr;
175                 case '<':
176                         outptr = g_stpcpy (outptr, "&lt;");
177                         priv->column++;
178                         break;
179                 case '>':
180                         outptr = g_stpcpy (outptr, "&gt;");
181                         priv->column++;
182                         break;
183                 case '&':
184                         outptr = g_stpcpy (outptr, "&amp;");
185                         priv->column++;
186                         break;
187                 case '"':
188                         outptr = g_stpcpy (outptr, "&quot;");
189                         priv->column++;
190                         break;
191                 case '\t':
192                         if (priv->flags & (CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES)) {
193                                 do {
194                                         outptr = check_size (mime_filter, outptr, outend, 7);
195                                         outptr = g_stpcpy (outptr, "&nbsp;");
196                                         priv->column++;
197                                 } while (priv->column % 8);
198                                 break;
199                         }
200                         /* otherwise, FALL THROUGH */
201                 case ' ':
202                         if (priv->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES
203                             && ((inptr == (in + 1) || (inptr < inend && (*inptr == ' ' || *inptr == '\t'))))) {
204                                 outptr = g_stpcpy (outptr, "&nbsp;");
205                                 priv->column++;
206                                 break;
207                         }
208                         /* otherwise, FALL THROUGH */
209                 default:
210                         if (u >= 20 && u <0x80)
211                                 *outptr++ = u;
212                         else {
213                                 if (priv->flags & CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT)
214                                         *outptr++ = '?';
215                                 else
216                                         outptr += sprintf (outptr, "&#%u;", u);
217                         }
218                         priv->column++;
219                         break;
220                 }
221         }
222
223         return outptr;
224 }
225
226 static void
227 html_convert (CamelMimeFilter *mime_filter,
228               const gchar *in,
229               gsize inlen,
230               gsize prespace,
231               gchar **out,
232               gsize *outlen,
233               gsize *outprespace,
234               gboolean flush)
235 {
236         CamelMimeFilterToHTMLPrivate *priv;
237         const gchar *inptr;
238         gchar *outptr, *outend;
239         const gchar *start;
240         const gchar *inend;
241         gint depth;
242
243         priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (mime_filter);
244
245         if (inlen == 0) {
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;
252
253                         *out = mime_filter->outbuf;
254                         *outlen = outptr - mime_filter->outbuf;
255                         *outprespace = mime_filter->outpre;
256                 } else {
257                         *out = (gchar *) in;
258                         *outlen = 0;
259                         *outprespace = 0;
260                 }
261
262                 return;
263         }
264
265         camel_mime_filter_set_size (mime_filter, inlen * 2 + 6, FALSE);
266
267         inptr = in;
268         inend = in + inlen;
269         outptr = mime_filter->outbuf;
270         outend = mime_filter->outbuf + mime_filter->outsize;
271
272         if (priv->flags & CAMEL_MIME_FILTER_TOHTML_PRE && !priv->pre_open) {
273                 outptr = g_stpcpy (outptr, "<pre>");
274                 priv->pre_open = TRUE;
275         }
276
277         start = inptr;
278         do {
279                 while (inptr < inend && *inptr != '\n')
280                         inptr++;
281
282                 if (inptr >= inend && !flush)
283                         break;
284
285                 priv->column = 0;
286                 depth = 0;
287
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 */
291
292                                 outptr = check_size (mime_filter, outptr, &outend, 25);
293                                 outptr += sprintf (outptr, "<font color=\"#%06x\">", (priv->color & 0xffffff));
294                         }
295 #if FOOLISHLY_UNMUNGE_FROM
296                         else if (*start == '>') {
297                                 /* >From line */
298                                 start++;
299                         }
300 #endif
301                 } else if (priv->flags & CAMEL_MIME_FILTER_TOHTML_CITE) {
302                         outptr = check_size (mime_filter, outptr, &outend, 6);
303                         outptr = g_stpcpy (outptr, "&gt; ");
304                         priv->column += 2;
305                 }
306
307 #define CONVERT_URLS (CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES)
308                 if (priv->flags & CONVERT_URLS) {
309                         gsize matchlen, len;
310                         urlmatch_t match;
311
312                         len = inptr - start;
313
314                         do {
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 */
317                                         outptr = writeln (
318                                                 mime_filter,
319                                                 (const guchar *) start,
320                                                 (const guchar *) start +
321                                                 match.um_so,
322                                                 outptr, &outend);
323
324                                         start += match.um_so;
325                                         len -= match.um_so;
326
327                                         matchlen = match.um_eo - match.um_so;
328
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 */
332                                         outptr = writeln (
333                                                 mime_filter,
334                                                 (const guchar *) match.prefix,
335                                                 (const guchar *) match.prefix +
336                                                 strlen (match.prefix),
337                                                 outptr, &outend);
338                                         outptr = writeln (
339                                                 mime_filter,
340                                                 (const guchar *) start,
341                                                 (const guchar *) start +
342                                                 matchlen,
343                                                 outptr, &outend);
344                                         outptr = append_string_verbatim (
345                                                 mime_filter, "\">",
346                                                 outptr, &outend);
347
348                                         /* now write the matched string */
349                                         outptr = writeln (
350                                                 mime_filter,
351                                                 (const guchar *) start,
352                                                 (const guchar *) start +
353                                                 matchlen,
354                                                 outptr, &outend);
355                                         priv->column += matchlen;
356                                         start += matchlen;
357                                         len -= matchlen;
358
359                                         /* close the href tag */
360                                         outptr = append_string_verbatim (
361                                                 mime_filter, "</a>",
362                                                 outptr, &outend);
363                                 } else {
364                                         /* nothing matched so write out the remainder of this line buffer */
365                                         outptr = writeln (
366                                                 mime_filter,
367                                                 (const guchar *) start,
368                                                 (const guchar *) start + len,
369                                                 outptr, &outend);
370                                         break;
371                                 }
372                         } while (len > 0);
373                 } else {
374                         outptr = writeln (
375                                 mime_filter,
376                                 (const guchar *) start,
377                                 (const guchar *) inptr,
378                                 outptr, &outend);
379                 }
380
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>");
384                 }
385
386                 if (inptr < inend) {
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>");
390                         }
391
392                         *outptr++ = '\n';
393                 }
394
395                 start = ++inptr;
396         } while (inptr < inend);
397
398         if (flush) {
399                 /* flush the rest of our input buffer */
400                 if (start < inend)
401                         outptr = writeln (mime_filter, (const guchar *) start, (const guchar *) inend, outptr, &outend);
402
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>");
407                 }
408         } else if (start < inend) {
409                 /* backup */
410                 camel_mime_filter_backup (mime_filter, start, (unsigned) (inend - start));
411         }
412
413         *out = mime_filter->outbuf;
414         *outlen = outptr - mime_filter->outbuf;
415         *outprespace = mime_filter->outpre;
416 }
417
418 static void
419 mime_filter_tohtml_finalize (GObject *object)
420 {
421         CamelMimeFilterToHTMLPrivate *priv;
422
423         priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (object);
424
425         camel_url_scanner_free (priv->scanner);
426
427         /* Chain up to parent's finalize() method. */
428         G_OBJECT_CLASS (camel_mime_filter_tohtml_parent_class)->finalize (object);
429 }
430
431 static void
432 mime_filter_tohtml_filter (CamelMimeFilter *mime_filter,
433                            const gchar *in,
434                            gsize len,
435                            gsize prespace,
436                            gchar **out,
437                            gsize *outlen,
438                            gsize *outprespace)
439 {
440         html_convert (
441                 mime_filter, in, len, prespace,
442                 out, outlen, outprespace, FALSE);
443 }
444
445 static void
446 mime_filter_tohtml_complete (CamelMimeFilter *mime_filter,
447                              const gchar *in,
448                              gsize len,
449                              gsize prespace,
450                              gchar **out,
451                              gsize *outlen,
452                              gsize *outprespace)
453 {
454         html_convert (
455                 mime_filter, in, len, prespace,
456                 out, outlen, outprespace, TRUE);
457 }
458
459 static void
460 mime_filter_tohtml_reset (CamelMimeFilter *mime_filter)
461 {
462         CamelMimeFilterToHTMLPrivate *priv;
463
464         priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (mime_filter);
465
466         priv->column = 0;
467         priv->pre_open = FALSE;
468 }
469
470 static void
471 camel_mime_filter_tohtml_class_init (CamelMimeFilterToHTMLClass *class)
472 {
473         GObjectClass *object_class;
474         CamelMimeFilterClass *filter_class;
475
476         g_type_class_add_private (class, sizeof (CamelMimeFilterToHTMLPrivate));
477
478         object_class = G_OBJECT_CLASS (class);
479         object_class->finalize = mime_filter_tohtml_finalize;
480
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;
485 }
486
487 static void
488 camel_mime_filter_tohtml_init (CamelMimeFilterToHTML *filter)
489 {
490         filter->priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (filter);
491         filter->priv->scanner = camel_url_scanner_new ();
492 }
493
494 /**
495  * camel_mime_filter_tohtml_new:
496  * @flags: bitwise flags defining the behaviour
497  * @color: color to use when highlighting quoted text
498  *
499  * Create a new #CamelMimeFilterToHTML object to convert plain text
500  * into HTML.
501  *
502  * Returns: a new #CamelMimeFilterToHTML object
503  **/
504 CamelMimeFilter *
505 camel_mime_filter_tohtml_new (guint32 flags,
506                               guint32 color)
507 {
508         CamelMimeFilter *filter;
509         CamelMimeFilterToHTMLPrivate *priv;
510         gint i;
511
512         filter = g_object_new (CAMEL_TYPE_MIME_FILTER_TOHTML, NULL);
513         priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (filter);
514
515         priv->flags = flags;
516         priv->color = color;
517
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);
522         }
523
524         return filter;
525 }
526
527 /**
528  * camel_text_to_html:
529  * @in: input text
530  * @flags: bitwise flags defining the html conversion behaviour
531  * @color: color to use when syntax highlighting
532  *
533  * Convert @in from plain text into HTML.
534  *
535  * Returns: a newly allocated string containing the HTMLified version
536  * of @in
537  **/
538 gchar *
539 camel_text_to_html (const gchar *in,
540                     guint32 flags,
541                     guint32 color)
542 {
543         CamelMimeFilter *filter;
544         gsize outlen, outpre;
545         gchar *outbuf;
546
547         g_return_val_if_fail (in != NULL, NULL);
548
549         filter = camel_mime_filter_tohtml_new (flags, color);
550
551         camel_mime_filter_complete (
552                 filter, (gchar *) in, strlen (in), 0,
553                 &outbuf, &outlen, &outpre);
554
555         outbuf = g_strndup (outbuf, outlen);
556
557         g_object_unref (filter);
558
559         return outbuf;
560 }