Fix FSF address (Tobias Mueller, #470445)
[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 2001 Ximian, Inc. (www.ximian.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
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdio.h>
29 #include <string.h>
30
31 #include "camel-mime-filter-tohtml.h"
32 #include "camel-url-scanner.h"
33 #include "camel-utf8.h"
34
35 /**
36  * TODO: convert common text/plain 'markup' to html. eg.:
37  *
38  * _word_ -> <u>_word_</u>
39  * *word* -> <b>*word*</b>
40  * /word/ -> <i>/word/</i>
41  **/
42
43 #define d(x)
44
45 #define FOOLISHLY_UNMUNGE_FROM 0
46
47 #define CONVERT_WEB_URLS  CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS
48 #define CONVERT_ADDRSPEC  CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES
49
50 static struct {
51         unsigned int mask;
52         urlpattern_t pattern;
53 } patterns[] = {
54         { CONVERT_WEB_URLS, { "file://",   "",        camel_url_file_start,     camel_url_file_end     } },
55         { CONVERT_WEB_URLS, { "ftp://",    "",        camel_url_web_start,      camel_url_web_end      } },
56         { CONVERT_WEB_URLS, { "sftp://",   "",        camel_url_web_start,      camel_url_web_end      } },
57         { CONVERT_WEB_URLS, { "http://",   "",        camel_url_web_start,      camel_url_web_end      } },
58         { CONVERT_WEB_URLS, { "https://",  "",        camel_url_web_start,      camel_url_web_end      } },
59         { CONVERT_WEB_URLS, { "news://",   "",        camel_url_web_start,      camel_url_web_end      } },
60         { CONVERT_WEB_URLS, { "nntp://",   "",        camel_url_web_start,      camel_url_web_end      } },
61         { CONVERT_WEB_URLS, { "telnet://", "",        camel_url_web_start,      camel_url_web_end      } },
62         { CONVERT_WEB_URLS, { "webcal://", "",        camel_url_web_start,      camel_url_web_end      } },
63         { CONVERT_WEB_URLS, { "mailto:",   "",        camel_url_web_start,      camel_url_web_end      } },
64         { CONVERT_WEB_URLS, { "callto:",   "",        camel_url_web_start,      camel_url_web_end      } },
65         { CONVERT_WEB_URLS, { "h323:",     "",        camel_url_web_start,      camel_url_web_end      } },
66         { CONVERT_WEB_URLS, { "sip:",      "",        camel_url_web_start,      camel_url_web_end      } },
67         { CONVERT_WEB_URLS, { "www.",      "http://", camel_url_web_start,      camel_url_web_end      } },
68         { CONVERT_WEB_URLS, { "ftp.",      "ftp://",  camel_url_web_start,      camel_url_web_end      } },
69         { CONVERT_ADDRSPEC, { "@",         "mailto:", camel_url_addrspec_start, camel_url_addrspec_end } },
70 };
71
72 #define NUM_URL_PATTERNS (sizeof (patterns) / sizeof (patterns[0]))
73
74 static void camel_mime_filter_tohtml_class_init (CamelMimeFilterToHTMLClass *klass);
75 static void camel_mime_filter_tohtml_init       (CamelMimeFilterToHTML *filter);
76 static void camel_mime_filter_tohtml_finalize   (CamelObject *obj);
77
78 static CamelMimeFilterClass *camel_mime_filter_tohtml_parent;
79
80
81 CamelType
82 camel_mime_filter_tohtml_get_type (void)
83 {
84         static CamelType type = CAMEL_INVALID_TYPE;
85         
86         if (type == CAMEL_INVALID_TYPE) {
87                 type = camel_type_register (camel_mime_filter_get_type (),
88                                             "CamelMimeFilterToHTML",
89                                             sizeof (CamelMimeFilterToHTML),
90                                             sizeof (CamelMimeFilterToHTMLClass),
91                                             (CamelObjectClassInitFunc) camel_mime_filter_tohtml_class_init,
92                                             NULL,
93                                             (CamelObjectInitFunc) camel_mime_filter_tohtml_init,
94                                             (CamelObjectFinalizeFunc) camel_mime_filter_tohtml_finalize);
95         }
96         
97         return type;
98 }
99
100 static void
101 camel_mime_filter_tohtml_finalize (CamelObject *obj)
102 {
103         CamelMimeFilterToHTML *filter = (CamelMimeFilterToHTML *) obj;
104         
105         camel_url_scanner_free (filter->scanner);
106 }
107
108 static void
109 camel_mime_filter_tohtml_init (CamelMimeFilterToHTML *filter)
110 {
111         filter->scanner = camel_url_scanner_new ();
112         
113         filter->flags = 0;
114         filter->colour = 0;
115         filter->column = 0;
116         filter->pre_open = FALSE;
117 }
118
119
120 static char *
121 check_size (CamelMimeFilter *filter, char *outptr, char **outend, size_t len)
122 {
123         size_t offset;
124         
125         if (*outend - outptr >= len)
126                 return outptr;
127         
128         offset = outptr - filter->outbuf;
129         
130         camel_mime_filter_set_size (filter, filter->outsize + len, TRUE);
131         
132         *outend = filter->outbuf + filter->outsize;
133         
134         return filter->outbuf + offset;
135 }
136
137 static int
138 citation_depth (const char *in)
139 {
140         register const char *inptr = in;
141         int depth = 1;
142         
143         if (*inptr++ != '>')
144                 return 0;
145         
146 #if FOOLISHLY_UNMUNGE_FROM
147         /* check that it isn't an escaped From line */
148         if (!strncmp (inptr, "From", 4))
149                 return 0;
150 #endif
151         
152         while (*inptr != '\n') {
153                 if (*inptr == ' ')
154                         inptr++;
155                 
156                 if (*inptr++ != '>')
157                         break;
158                 
159                 depth++;
160         }
161         
162         return depth;
163 }
164
165 static char *
166 writeln (CamelMimeFilter *filter, const char *in, const char *inend, char *outptr, char **outend)
167 {
168         CamelMimeFilterToHTML *html = (CamelMimeFilterToHTML *) filter;
169         const char *inptr = in;
170         
171         while (inptr < inend) {
172                 guint32 u;
173                 
174                 outptr = check_size (filter, outptr, outend, 16);
175
176                 u = camel_utf8_getc_limit (&inptr, (const unsigned char *) inend);
177                 switch (u) {
178                 case 0xffff:
179                         g_warning("Truncated utf8 buffer");
180                         return outptr;
181                 case '<':
182                         outptr = g_stpcpy (outptr, "&lt;");
183                         html->column++;
184                         break;
185                 case '>':
186                         outptr = g_stpcpy (outptr, "&gt;");
187                         html->column++;
188                         break;
189                 case '&':
190                         outptr = g_stpcpy (outptr, "&amp;");
191                         html->column++;
192                         break;
193                 case '"':
194                         outptr = g_stpcpy (outptr, "&quot;");
195                         html->column++;
196                         break;
197                 case '\t':
198                         if (html->flags & (CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES)) {
199                                 do {
200                                         outptr = check_size (filter, outptr, outend, 7);
201                                         outptr = g_stpcpy (outptr, "&nbsp;");
202                                         html->column++;
203                                 } while (html->column % 8);
204                                 break;
205                         }
206                         /* otherwise, FALL THROUGH */
207                 case ' ':
208                         if (html->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES
209                             && ((inptr == (in + 1) || (inptr < inend && (*inptr == ' ' || *inptr == '\t'))))) {
210                                 outptr = g_stpcpy (outptr, "&nbsp;");
211                                 html->column++;
212                                 break;
213                         }
214                         /* otherwise, FALL THROUGH */
215                 default:
216                         if (u >= 20 && u <0x80)
217                                 *outptr++ = u;
218                         else {
219                                 if (html->flags & CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT)
220                                         *outptr++ = '?';
221                                 else
222                                         outptr += sprintf(outptr, "&#%u;", u);
223                         }
224                         html->column++;
225                         break;
226                 }
227         }
228         
229         return outptr;
230 }
231
232 static void
233 html_convert (CamelMimeFilter *filter, char *in, size_t inlen, size_t prespace,
234               char **out, size_t *outlen, size_t *outprespace, gboolean flush)
235 {
236         CamelMimeFilterToHTML *html = (CamelMimeFilterToHTML *) filter;
237         register char *inptr, *outptr;
238         char *start, *outend;
239         const char *inend;
240         int depth;
241
242         if (inlen == 0) {
243                 if (html->pre_open) {
244                         /* close the pre-tag */
245                         outend = filter->outbuf + filter->outsize;
246                         outptr = check_size (filter, filter->outbuf, &outend, 10);
247                         outptr = g_stpcpy (outptr, "</pre>");
248                         html->pre_open = FALSE;
249
250                         *out = filter->outbuf;
251                         *outlen = outptr - filter->outbuf;
252                         *outprespace = filter->outpre;
253                 } else {
254                         *out = in;
255                         *outlen = 0;
256                         *outprespace = 0;
257                 }
258
259                 return;
260         }
261         
262         camel_mime_filter_set_size (filter, inlen * 2 + 6, FALSE);
263         
264         inptr = in;
265         inend = in + inlen;
266         outptr = filter->outbuf;
267         outend = filter->outbuf + filter->outsize;
268         
269         if (html->flags & CAMEL_MIME_FILTER_TOHTML_PRE && !html->pre_open) {
270                 outptr = g_stpcpy (outptr, "<pre>");
271                 html->pre_open = TRUE;
272         }
273
274         start = inptr;
275         do {
276                 while (inptr < inend && *inptr != '\n')
277                         inptr++;
278
279                 if (inptr >= inend && !flush)
280                         break;
281
282                 html->column = 0;
283                 depth = 0;
284                 
285                 if (html->flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) {
286                         if ((depth = citation_depth (start)) > 0) {
287                                 /* FIXME: we could easily support multiple colour depths here */
288                                 
289                                 outptr = check_size (filter, outptr, &outend, 25);
290                                 outptr += sprintf(outptr, "<font color=\"#%06x\">", (html->colour & 0xffffff));
291                         }
292 #if FOOLISHLY_UNMUNGE_FROM
293                         else if (*start == '>') {
294                                 /* >From line */
295                                 start++;
296                         }
297 #endif
298                 } else if (html->flags & CAMEL_MIME_FILTER_TOHTML_CITE) {
299                         outptr = check_size (filter, outptr, &outend, 6);
300                         outptr = g_stpcpy (outptr, "&gt; ");
301                         html->column += 2;
302                 }
303                 
304 #define CONVERT_URLS (CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES)
305                 if (html->flags & CONVERT_URLS) {
306                         size_t matchlen, buflen, len;
307                         urlmatch_t match;
308                         
309                         len = inptr - start;
310                         
311                         do {
312                                 if (camel_url_scanner_scan (html->scanner, start, len, &match)) {
313                                         /* write out anything before the first regex match */
314                                         outptr = writeln (filter, start, start + match.um_so,
315                                                           outptr, &outend);
316                                         
317                                         start += match.um_so;
318                                         len -= match.um_so;
319                                         
320                                         matchlen = match.um_eo - match.um_so;
321                                         
322                                         buflen = 20 + strlen (match.prefix) + matchlen + matchlen;
323                                         outptr = check_size (filter, outptr, &outend, buflen);
324                                         
325                                         /* write out the href tag */
326                                         outptr = g_stpcpy (outptr, "<a href=\"");
327                                         outptr = g_stpcpy (outptr, match.prefix);
328                                         memcpy (outptr, start, matchlen);
329                                         outptr += matchlen;
330                                         outptr = g_stpcpy (outptr, "\">");
331                                         
332                                         /* now write the matched string */
333                                         memcpy (outptr, start, matchlen);
334                                         html->column += matchlen;
335                                         outptr += matchlen;
336                                         start += matchlen;
337                                         len -= matchlen;
338                                         
339                                         /* close the href tag */
340                                         outptr = g_stpcpy (outptr, "</a>");
341                                 } else {
342                                         /* nothing matched so write out the remainder of this line buffer */
343                                         outptr = writeln (filter, start, start + len, outptr, &outend);
344                                         break;
345                                 }
346                         } while (len > 0);
347                 } else {
348                         outptr = writeln (filter, start, inptr, outptr, &outend);
349                 }
350                 
351                 if ((html->flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) && depth > 0) {
352                         outptr = check_size (filter, outptr, &outend, 8);
353                         outptr = g_stpcpy (outptr, "</font>");
354                 }
355                 
356                 if (inptr < inend) {
357                         if (html->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_NL) {
358                                 outptr = check_size (filter, outptr, &outend, 5);
359                                 outptr = g_stpcpy (outptr, "<br>");
360                         }
361                         
362                         *outptr++ = '\n';
363                 }
364                 
365                 start = ++inptr;
366         } while (inptr < inend);
367         
368         if (flush) {
369                 /* flush the rest of our input buffer */
370                 if (start < inend)
371                         outptr = writeln (filter, start, inend, outptr, &outend);
372                 
373                 if (html->pre_open) {
374                         /* close the pre-tag */
375                         outptr = check_size (filter, outptr, &outend, 10);
376                         outptr = g_stpcpy (outptr, "</pre>");
377                 }
378         } else if (start < inend) {
379                 /* backup */
380                 camel_mime_filter_backup (filter, start, (unsigned) (inend - start));
381         }
382         
383         *out = filter->outbuf;
384         *outlen = outptr - filter->outbuf;
385         *outprespace = filter->outpre;
386 }
387
388 static void
389 filter_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
390                char **out, size_t *outlen, size_t *outprespace)
391 {
392         html_convert (filter, in, len, prespace, out, outlen, outprespace, FALSE);
393 }
394
395 static void 
396 filter_complete (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
397                  char **out, size_t *outlen, size_t *outprespace)
398 {
399         html_convert (filter, in, len, prespace, out, outlen, outprespace, TRUE);
400 }
401
402 static void
403 filter_reset (CamelMimeFilter *filter)
404 {
405         CamelMimeFilterToHTML *html = (CamelMimeFilterToHTML *) filter;
406         
407         html->column = 0;
408         html->pre_open = FALSE;
409 }
410
411 static void
412 camel_mime_filter_tohtml_class_init (CamelMimeFilterToHTMLClass *klass)
413 {
414         CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass;
415         
416         camel_mime_filter_tohtml_parent = CAMEL_MIME_FILTER_CLASS (camel_type_get_global_classfuncs (camel_mime_filter_get_type ()));
417         
418         filter_class->reset = filter_reset;
419         filter_class->filter = filter_filter;
420         filter_class->complete = filter_complete;
421 }
422
423
424 /**
425  * camel_mime_filter_tohtml_new:
426  * @flags: bitwise flags defining the behaviour
427  * @colour: colour to use when highlighting quoted text
428  *
429  * Create a new #CamelMimeFilterToHTML object to convert plain text
430  * into HTML.
431  *
432  * Returns a new #CamelMimeFilterToHTML object
433  **/
434 CamelMimeFilter *
435 camel_mime_filter_tohtml_new (guint32 flags, guint32 colour)
436 {
437         CamelMimeFilterToHTML *new;
438         int i;
439         
440         new = CAMEL_MIME_FILTER_TOHTML (camel_object_new (camel_mime_filter_tohtml_get_type ()));
441         
442         new->flags = flags;
443         new->colour = colour;
444         
445         for (i = 0; i < NUM_URL_PATTERNS; i++) {
446                 if (patterns[i].mask & flags)
447                         camel_url_scanner_add (new->scanner, &patterns[i].pattern);
448         }
449         
450         return CAMEL_MIME_FILTER (new);
451 }
452
453
454 /**
455  * camel_text_to_html:
456  * @in: input text
457  * @flags: bitwise flags defining the html conversion behaviour
458  * @colour: colour to use when syntax highlighting
459  *
460  * Convert @in from plain text into HTML.
461  *
462  * Returns a newly allocated string containing the HTMLified version
463  * of @in
464  **/
465 char *
466 camel_text_to_html (const char *in, guint32 flags, guint32 colour)
467 {
468         CamelMimeFilter *filter;
469         size_t outlen, outpre;
470         char *outbuf;
471         
472         g_return_val_if_fail (in != NULL, NULL);
473         
474         filter = camel_mime_filter_tohtml_new (flags, colour);
475         
476         camel_mime_filter_complete (filter, (char *) in, strlen (in), 0,
477                                     &outbuf, &outlen, &outpre);
478         
479         outbuf = g_strndup (outbuf, outlen);
480         
481         camel_object_unref (filter);
482         
483         return outbuf;
484 }