2007-05-11 Murray Cumming <murrayc@murrayc.com>
[modest] / src / modest-text-utils.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30
31 #include <glib.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <glib/gi18n.h>
35 #include <regex.h>
36 #include <modest-tny-platform-factory.h>
37 #include <modest-text-utils.h>
38 #include <modest-runtime.h>
39
40
41 #ifdef HAVE_CONFIG_H
42 #include <config.h>
43 #endif /*HAVE_CONFIG_H */
44
45 /* defines */
46 #define FORWARD_STRING _("-----Forwarded Message-----")
47 #define FROM_STRING _("From:")
48 #define SENT_STRING _("Sent:")
49 #define TO_STRING _("To:")
50 #define SUBJECT_STRING _("Subject:")
51 #define EMPTY_STRING ""
52
53 /*
54  * we need these regexps to find URLs in plain text e-mails
55  */
56 typedef struct _url_match_pattern_t url_match_pattern_t;
57 struct _url_match_pattern_t {
58         gchar   *regex;
59         regex_t *preg;
60         gchar   *prefix;
61 };
62
63 typedef struct _url_match_t url_match_t;
64 struct _url_match_t {
65         guint offset;
66         guint len;
67         const gchar* prefix;
68 };
69
70 #define MAIL_VIEWER_URL_MATCH_PATTERNS  {                               \
71         { "(file|rtsp|http|ftp|https)://[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]+[-A-Za-z0-9_$%&=?/~#]",\
72           NULL, NULL },\
73         { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
74           NULL, "http://" },\
75         { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
76           NULL, "ftp://" },\
77         { "(voipto|callto|chatto|jabberto|xmpp):[-_a-z@0-9.\\+]+", \
78            NULL, NULL},                                             \
79         { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+",                    \
80           NULL, NULL},\
81         { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\
82           NULL, "mailto:"}\
83         }
84
85 /* private */
86 static gchar*   cite                    (const time_t sent_date, const gchar *from);
87 static void     hyperlinkify_plain_text (GString *txt);
88 static gint     cmp_offsets_reverse     (const url_match_t *match1, const url_match_t *match2);
89 static void     chk_partial_match       (const url_match_t *match, guint* offset);
90 static GSList*  get_url_matches         (GString *txt);
91
92 static GString* get_next_line           (const char *b, const gsize blen, const gchar * iter);
93 static int      get_indent_level        (const char *l);
94 static void     unquote_line            (GString * l);
95 static void     append_quoted           (GString * buf, const int indent, const GString * str, 
96                                          const int cutpoint);
97 static int      get_breakpoint_utf8     (const gchar * s, const gint indent, const gint limit);
98 static int      get_breakpoint_ascii    (const gchar * s, const gint indent, const gint limit);
99 static int      get_breakpoint          (const gchar * s, const gint indent, const gint limit);
100
101 static gchar*   modest_text_utils_quote_plain_text (const gchar *text, 
102                                                     const gchar *cite, 
103                                                     int limit);
104
105 static gchar*   modest_text_utils_quote_html       (const gchar *text, 
106                                                     const gchar *cite, 
107                                                     int limit);
108
109
110 /* ******************************************************************* */
111 /* ************************* PUBLIC FUNCTIONS ************************ */
112 /* ******************************************************************* */
113
114 gchar *
115 modest_text_utils_quote (const gchar *text, 
116                          const gchar *content_type,
117                          const gchar *signature,
118                          const gchar *from,
119                          const time_t sent_date, 
120                          int limit)
121 {
122         gchar *retval, *cited;
123
124         g_return_val_if_fail (text, NULL);
125         g_return_val_if_fail (content_type, NULL);
126
127         cited = cite (sent_date, from);
128         
129         if (content_type && strcmp (content_type, "text/html") == 0)
130                 /* TODO: extract the <body> of the HTML and pass it to
131                    the function */
132                 retval = modest_text_utils_quote_html (text, cited, limit);
133         else
134                 retval = modest_text_utils_quote_plain_text (text, cited, limit);
135         
136         g_free (cited);
137
138         return retval;
139 }
140
141
142 gchar *
143 modest_text_utils_cite (const gchar *text,
144                         const gchar *content_type,
145                         const gchar *signature,
146                         const gchar *from,
147                         time_t sent_date)
148 {
149         gchar *tmp, *retval;
150         gchar *tmp_sig;
151
152         g_return_val_if_fail (text, NULL);
153         g_return_val_if_fail (content_type, NULL);
154
155         if (!signature)
156                 tmp_sig = g_strdup ("");
157         else if (!strcmp(content_type, "text/html")) {
158                 tmp_sig = modest_text_utils_convert_to_html_body(signature);
159         } else {
160                 tmp_sig = g_strdup (signature);
161         }
162
163         tmp = cite (sent_date, from);
164         retval = g_strdup_printf ("%s%s%s\n", tmp_sig, tmp, text);
165         g_free (tmp_sig);
166         g_free (tmp);
167
168         return retval;
169 }
170
171 gchar * 
172 modest_text_utils_inline (const gchar *text,
173                           const gchar *content_type,
174                           const gchar *signature,
175                           const gchar *from,
176                           time_t sent_date,
177                           const gchar *to,
178                           const gchar *subject)
179 {
180         gchar sent_str[101];
181         gchar *formatted_signature;
182         const gchar *plain_format = "%s%s\n%s %s\n%s %s\n%s %s\n%s %s\n\n%s";
183         const gchar *html_format = \
184                 "%s%s<br>\n<table width=\"100%\" border=\"0\" cellspacing=\"2\" cellpadding=\"2\">\n" \
185                 "<tr><td>%s</td><td>%s</td></tr>\n" \
186                 "<tr><td>%s</td><td>%s</td></tr>\n" \
187                 "<tr><td>%s</td><td>%s</td></tr>\n" \
188                 "<tr><td>%s</td><td>%s</td></tr>\n" \
189                 "<br><br>%s";
190         const gchar *format;
191
192         g_return_val_if_fail (text, NULL);
193         g_return_val_if_fail (content_type, NULL);
194         g_return_val_if_fail (text, NULL);
195         
196         modest_text_utils_strftime (sent_str, 100, "%c", sent_date);
197
198         if (!strcmp (content_type, "text/html"))
199                 /* TODO: extract the <body> of the HTML and pass it to
200                    the function */
201                 format = html_format;
202         else
203                 format = plain_format;
204
205         if (signature != NULL) {
206                 if (!strcmp (content_type, "text/html")) {
207                         formatted_signature = g_strconcat (signature, "<br/>", NULL);
208                 } else {
209                         formatted_signature = g_strconcat (signature, "\n", NULL);
210                 }
211         } else {
212                 formatted_signature = "";
213         }
214
215         return g_strdup_printf (format, formatted_signature, 
216                                 FORWARD_STRING,
217                                 FROM_STRING, (from) ? from : EMPTY_STRING,
218                                 SENT_STRING, sent_str,
219                                 TO_STRING, (to) ? to : EMPTY_STRING,
220                                 SUBJECT_STRING, (subject) ? subject : EMPTY_STRING,
221                                 text);
222 }
223
224 /* just to prevent warnings:
225  * warning: `%x' yields only last 2 digits of year in some locales
226  */
227 gsize
228 modest_text_utils_strftime(char *s, gsize max, const char *fmt, time_t timet)
229 {
230         struct tm tm;
231
232         /* does not work on old maemo glib: 
233          *   g_date_set_time_t (&date, timet);
234          */
235         localtime_r (&timet, &tm);
236
237         return strftime(s, max, fmt, &tm);
238 }
239
240 gchar *
241 modest_text_utils_derived_subject (const gchar *subject, const gchar *prefix)
242 {
243         gchar *tmp;
244
245         g_return_val_if_fail (prefix, NULL);
246         
247         if (!subject)
248                 return g_strdup (prefix);
249
250         tmp = g_strchug (g_strdup (subject));
251
252         if (!strncmp (tmp, prefix, strlen (prefix))) {
253                 return tmp;
254         } else {
255                 g_free (tmp);
256                 return g_strdup_printf ("%s %s", prefix, subject);
257         }
258 }
259
260 gchar*
261 modest_text_utils_remove_address (const gchar *address_list, const gchar *address)
262 {
263         gchar *dup, *token, *ptr, *result;
264         GString *filtered_emails;
265
266         g_return_val_if_fail (address_list, NULL);
267
268         if (!address)
269                 return g_strdup (address_list);
270         
271         /* search for substring */
272         if (!strstr ((const char *) address_list, (const char *) address))
273                 return g_strdup (address_list);
274
275         dup = g_strdup (address_list);
276         filtered_emails = g_string_new (NULL);
277         
278         token = strtok_r (dup, ",", &ptr);
279
280         while (token != NULL) {
281                 /* Add to list if not found */
282                 if (!strstr ((const char *) token, (const char *) address)) {
283                         if (filtered_emails->len == 0)
284                                 g_string_append_printf (filtered_emails, "%s", g_strstrip (token));
285                         else
286                                 g_string_append_printf (filtered_emails, ",%s", g_strstrip (token));
287                 }
288                 token = strtok_r (NULL, ",", &ptr);
289         }
290         result = filtered_emails->str;
291
292         /* Clean */
293         g_free (dup);
294         g_string_free (filtered_emails, FALSE);
295
296         return result;
297 }
298
299 static void
300 modest_text_utils_convert_buffer_to_html (GString *html, const gchar *data)
301 {
302         guint            i;
303         gboolean        space_seen = FALSE;
304         gsize           len;
305
306         len = strlen (data);
307
308         /* replace with special html chars where needed*/
309         for (i = 0; i != len; ++i)  {
310                 char kar = data[i];
311                 
312                 if (space_seen && kar != ' ') {
313                         g_string_append_c (html, ' ');
314                         space_seen = FALSE;
315                 }
316                 
317                 switch (kar) {
318                 case 0:  break; /* ignore embedded \0s */       
319                 case '<'  : g_string_append (html, "&lt;");   break;
320                 case '>'  : g_string_append (html, "&gt;");   break;
321                 case '&'  : g_string_append (html, "&amp;");  break;
322                 case '"'  : g_string_append (html, "&quot;");  break;
323                 case '\'' : g_string_append (html, "&apos;"); break;
324                 case '\n' : g_string_append (html, "<br>\n");  break;
325                 case '\t' : g_string_append (html, "&nbsp;&nbsp;&nbsp; "); break; /* note the space at the end*/
326                 case ' ':
327                         if (space_seen) { /* second space in a row */
328                                 g_string_append (html, "&nbsp ");
329                                 space_seen = FALSE;
330                         } else
331                                 space_seen = TRUE;
332                         break;
333                 default:
334                         g_string_append_c (html, kar);
335                 }
336         }
337 }
338
339 gchar*
340 modest_text_utils_convert_to_html (const gchar *data)
341 {
342         GString         *html;      
343         gsize           len;
344         
345         if (!data)
346                 return NULL;
347
348         len = strlen (data);
349         html = g_string_sized_new (1.5 * len);  /* just a  guess... */
350
351         g_string_append_printf (html,
352                                 "<html><head>"
353                                 "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf8\">"
354                                 "</head>"
355                                 "<body>");
356
357         modest_text_utils_convert_buffer_to_html (html, data);
358         
359         g_string_append (html, "</body></html>");
360         hyperlinkify_plain_text (html);
361
362         return g_string_free (html, FALSE);
363 }
364
365 gchar *
366 modest_text_utils_convert_to_html_body (const gchar *data)
367 {
368         GString         *html;      
369         gsize           len;
370         
371         if (!data)
372                 return NULL;
373
374         len = strlen (data);
375         html = g_string_sized_new (1.5 * len);  /* just a  guess... */
376
377         modest_text_utils_convert_buffer_to_html (html, data);
378
379         hyperlinkify_plain_text (html);
380
381         return g_string_free (html, FALSE);
382 }
383
384 void
385 modest_text_utils_get_addresses_indexes (const gchar *addresses, GSList **start_indexes, GSList **end_indexes)
386 {
387         gchar *current, *start, *last_blank;
388         gint start_offset = 0, current_offset = 0;
389
390         g_return_if_fail (start_indexes != NULL);
391         g_return_if_fail (end_indexes != NULL);
392
393         start = (gchar *) addresses;
394         current = start;
395         last_blank = start;
396
397         while (*current != '\0') {
398                 if ((start == current)&&((*current == ' ')||(*current == ',')||(*current == ';'))) {
399                         start = g_utf8_next_char (start);
400                         start_offset++;
401                         last_blank = current;
402                 } else if ((*current == ',')||(*current == ';')) {
403                         gint *start_index, *end_index;
404                         start_index = g_new0(gint, 1);
405                         end_index = g_new0(gint, 1);
406                         *start_index = start_offset;
407                         *end_index = current_offset;
408                         *start_indexes = g_slist_prepend (*start_indexes, start_index);
409                         *end_indexes = g_slist_prepend (*end_indexes, end_index);
410                         start = g_utf8_next_char (current);
411                         start_offset = current_offset + 1;
412                         last_blank = start;
413                 } else if (*current == '"') {
414                         current = g_utf8_next_char (current);
415                         current_offset ++;
416                         while ((*current != '"')&&(*current != '\0')) {
417                                 current = g_utf8_next_char (current);
418                                 current_offset ++;
419                         }
420                 }
421                                 
422                 current = g_utf8_next_char (current);
423                 current_offset ++;
424         }
425
426         if (start != current) {
427                         gint *start_index, *end_index;
428                         start_index = g_new0(gint, 1);
429                         end_index = g_new0(gint, 1);
430                         *start_index = start_offset;
431                         *end_index = current_offset;
432                         *start_indexes = g_slist_prepend (*start_indexes, start_index);
433                         *end_indexes = g_slist_prepend (*end_indexes, end_index);
434         }
435         
436         *start_indexes = g_slist_reverse (*start_indexes);
437         *end_indexes = g_slist_reverse (*end_indexes);
438
439         return;
440 }
441
442 GSList *
443 modest_text_utils_split_addresses_list (const gchar *addresses)
444 {
445         gchar *current, *start, *last_blank;
446         GSList *result = NULL;
447
448         start = (gchar *) addresses;
449         current = start;
450         last_blank = start;
451
452         while (*current != '\0') {
453                 if ((start == current)&&((*current == ' ')||(*current == ',')||(*current == ';'))) {
454                         start = g_utf8_next_char (start);
455                         last_blank = current;
456                 } else if ((*current == ',')||(*current == ';')) {
457                         gchar *new_address = NULL;
458                         new_address = g_strndup (start, current - last_blank);
459                         result = g_slist_prepend (result, new_address);
460                         start = g_utf8_next_char (current);
461                         last_blank = start;
462                 } else if (*current == '\"') {
463                         if (current == start) {
464                                 current = g_utf8_next_char (current);
465                                 start = g_utf8_next_char (start);
466                         }
467                         while ((*current != '\"')&&(*current != '\0'))
468                                 current = g_utf8_next_char (current);
469                 }
470                                 
471                 current = g_utf8_next_char (current);
472         }
473
474         if (start != current) {
475                 gchar *new_address = NULL;
476                 new_address = g_strndup (start, current - last_blank);
477                 result = g_slist_prepend (result, new_address);
478         }
479
480         result = g_slist_reverse (result);
481         return result;
482
483 }
484
485 void
486 modest_text_utils_address_range_at_position (const gchar *recipients_list,
487                                              gint position,
488                                              gint *start,
489                                              gint *end)
490 {
491         gchar *current = NULL;
492         gint range_start = 0;
493         gint range_end = 0;
494         gint index;
495         gboolean is_quoted = FALSE;
496
497         index = 0;
498         for (current = (gchar *) recipients_list; *current != '\0'; current = g_utf8_find_next_char (current, NULL)) {
499                 gunichar c = g_utf8_get_char (current);
500
501                 if ((c == ',') && (!is_quoted)) {
502                         if (index < position) {
503                                 range_start = index + 1;
504                         } else {
505                                 break;
506                         }
507                 } else if (c == '\"') {
508                         is_quoted = !is_quoted;
509                 } else if ((c == ' ') &&(range_start == index)) {
510                         range_start ++;
511                 }
512                 index ++;
513                 range_end = index;
514         }
515
516         if (start)
517                 *start = range_start;
518         if (end)
519                 *end = range_end;
520 }
521
522
523 /* ******************************************************************* */
524 /* ************************* UTILIY FUNCTIONS ************************ */
525 /* ******************************************************************* */
526
527 static GString *
528 get_next_line (const gchar * b, const gsize blen, const gchar * iter)
529 {
530         GString *gs;
531         const gchar *i0;
532         
533         if (iter > b + blen)
534                 return g_string_new("");
535         
536         i0 = iter;
537         while (iter[0]) {
538                 if (iter[0] == '\n')
539                         break;
540                 iter++;
541         }
542         gs = g_string_new_len (i0, iter - i0);
543         return gs;
544 }
545 static int
546 get_indent_level (const char *l)
547 {
548         int indent = 0;
549
550         while (l[0]) {
551                 if (l[0] == '>') {
552                         indent++;
553                         if (l[1] == ' ') {
554                                 l++;
555                         }
556                 } else {
557                         break;
558                 }
559                 l++;
560
561         }
562
563         /*      if we hit the signature marker "-- ", we return -(indent + 1). This
564          *      stops reformatting.
565          */
566         if (strcmp (l, "-- ") == 0) {
567                 return -1 - indent;
568         } else {
569                 return indent;
570         }
571 }
572
573 static void
574 unquote_line (GString * l)
575 {
576         gchar *p;
577
578         p = l->str;
579         while (p[0]) {
580                 if (p[0] == '>') {
581                         if (p[1] == ' ') {
582                                 p++;
583                         }
584                 } else {
585                         break;
586                 }
587                 p++;
588         }
589         g_string_erase (l, 0, p - l->str);
590 }
591
592 static void
593 append_quoted (GString * buf, int indent, const GString * str,
594                const int cutpoint)
595 {
596         int i;
597
598         indent = indent < 0 ? abs (indent) - 1 : indent;
599         for (i = 0; i <= indent; i++) {
600                 g_string_append (buf, "> ");
601         }
602         if (cutpoint > 0) {
603                 g_string_append_len (buf, str->str, cutpoint);
604         } else {
605                 g_string_append (buf, str->str);
606         }
607         g_string_append (buf, "\n");
608 }
609
610 static int
611 get_breakpoint_utf8 (const gchar * s, gint indent, const gint limit)
612 {
613         gint index = 0;
614         const gchar *pos, *last;
615         gunichar *uni;
616
617         indent = indent < 0 ? abs (indent) - 1 : indent;
618
619         last = NULL;
620         pos = s;
621         uni = g_utf8_to_ucs4_fast (s, -1, NULL);
622         while (pos[0]) {
623                 if ((index + 2 * indent > limit) && last) {
624                         g_free (uni);
625                         return last - s;
626                 }
627                 if (g_unichar_isspace (uni[index])) {
628                         last = pos;
629                 }
630                 pos = g_utf8_next_char (pos);
631                 index++;
632         }
633         g_free (uni);
634         return strlen (s);
635 }
636
637 static int
638 get_breakpoint_ascii (const gchar * s, const gint indent, const gint limit)
639 {
640         gint i, last;
641
642         last = strlen (s);
643         if (last + 2 * indent < limit)
644                 return last;
645
646         for (i = strlen (s); i > 0; i--) {
647                 if (s[i] == ' ') {
648                         if (i + 2 * indent <= limit) {
649                                 return i;
650                         } else {
651                                 last = i;
652                         }
653                 }
654         }
655         return last;
656 }
657
658 static int
659 get_breakpoint (const gchar * s, const gint indent, const gint limit)
660 {
661
662         if (g_utf8_validate (s, -1, NULL)) {
663                 return get_breakpoint_utf8 (s, indent, limit);
664         } else {                /* assume ASCII */
665                 //g_warning("invalid UTF-8 in msg");
666                 return get_breakpoint_ascii (s, indent, limit);
667         }
668 }
669
670 static gchar *
671 cite (const time_t sent_date, const gchar *from)
672 {
673         gchar sent_str[101];
674
675         /* format sent_date */
676         modest_text_utils_strftime (sent_str, 100, "%c", sent_date);
677         return g_strdup_printf (N_("On %s, %s wrote:\n"), 
678                                 sent_str, 
679                                 (from) ? from : EMPTY_STRING);
680 }
681
682
683 static gchar *
684 modest_text_utils_quote_plain_text (const gchar *text, 
685                                     const gchar *cite, 
686                                     int limit)
687 {
688         const gchar *iter;
689         gint indent, breakpoint, rem_indent = 0;
690         GString *q, *l, *remaining;
691         gsize len;
692
693         /* remaining will store the rest of the line if we have to break it */
694         q = g_string_new (cite);
695         remaining = g_string_new ("");
696
697         iter = text;
698         len = strlen(text);
699         do {
700                 l = get_next_line (text, len, iter);
701                 iter = iter + l->len + 1;
702                 indent = get_indent_level (l->str);
703                 unquote_line (l);
704
705                 if (remaining->len) {
706                         if (l->len && indent == rem_indent) {
707                                 g_string_prepend (l, " ");
708                                 g_string_prepend (l, remaining->str);
709                         } else {
710                                 do {
711                                         breakpoint =
712                                                 get_breakpoint (remaining->str,
713                                                                 rem_indent,
714                                                                 limit);
715                                         append_quoted (q, rem_indent,
716                                                        remaining, breakpoint);
717                                         g_string_erase (remaining, 0,
718                                                         breakpoint);
719                                         if (remaining->str[0] == ' ') {
720                                                 g_string_erase (remaining, 0,
721                                                                 1);
722                                         }
723                                 } while (remaining->len);
724                         }
725                 }
726                 g_string_free (remaining, TRUE);
727                 breakpoint = get_breakpoint (l->str, indent, limit);
728                 remaining = g_string_new (l->str + breakpoint);
729                 if (remaining->str[0] == ' ') {
730                         g_string_erase (remaining, 0, 1);
731                 }
732                 rem_indent = indent;
733                 append_quoted (q, indent, l, breakpoint);
734                 g_string_free (l, TRUE);
735         } while ((iter < text + len) || (remaining->str[0]));
736
737         return g_string_free (q, FALSE);
738 }
739
740 static gchar*
741 modest_text_utils_quote_html (const gchar *text, 
742                               const gchar *cite, 
743                               int limit)
744 {
745         const gchar *format = \
746                 "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" \
747                 "<html>\n" \
748                 "<body>\n" \
749                 "%s" \
750                 "<blockquote type=\"cite\">\n%s\n</blockquote>\n" \
751                 "</body>\n" \
752                 "</html>\n";
753
754         return g_strdup_printf (format, cite, text);
755 }
756
757 static gint 
758 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
759 {
760         return match2->offset - match1->offset;
761 }
762
763
764
765 /*
766  * check if the match is inside an existing match... */
767 static void
768 chk_partial_match (const url_match_t *match, guint* offset)
769 {
770         if (*offset >= match->offset && *offset < match->offset + match->len)
771                 *offset = -1;
772 }
773
774 static GSList*
775 get_url_matches (GString *txt)
776 {
777         regmatch_t rm;
778         guint rv, i, offset = 0;
779         GSList *match_list = NULL;
780
781         static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
782         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
783
784         /* initalize the regexps */
785         for (i = 0; i != pattern_num; ++i) {
786                 patterns[i].preg = g_slice_new0 (regex_t);
787
788                 /* this should not happen */
789                 g_return_val_if_fail (regcomp (patterns[i].preg, patterns[i].regex,
790                                                REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0, NULL);
791         }
792         /* find all the matches */
793         for (i = 0; i != pattern_num; ++i) {
794                 offset     = 0; 
795                 while (1) {
796                         int test_offset;
797                         if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) {
798                                 g_return_val_if_fail (rv == REG_NOMATCH, NULL); /* this should not happen */
799                                 break; /* try next regexp */ 
800                         }
801                         if (rm.rm_so == -1)
802                                 break;
803
804                         /* FIXME: optimize this */
805                         /* to avoid partial matches on something that was already found... */
806                         /* check_partial_match will put -1 in the data ptr if that is the case */
807                         test_offset = offset + rm.rm_so;
808                         g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset);
809                         
810                         /* make a list of our matches (<offset, len, prefix> tupels)*/
811                         if (test_offset != -1) {
812                                 url_match_t *match = g_slice_new (url_match_t);
813                                 match->offset = offset + rm.rm_so;
814                                 match->len    = rm.rm_eo - rm.rm_so;
815                                 match->prefix = patterns[i].prefix;
816                                 match_list = g_slist_prepend (match_list, match);
817                         }
818                         offset += rm.rm_eo;
819                 }
820         }
821
822         for (i = 0; i != pattern_num; ++i) {
823                 regfree (patterns[i].preg);
824                 g_slice_free  (regex_t, patterns[i].preg);
825         } /* don't free patterns itself -- it's static */
826         
827         /* now sort the list, so the matches are in reverse order of occurence.
828          * that way, we can do the replacements starting from the end, so we don't need
829          * to recalculate the offsets
830          */
831         match_list = g_slist_sort (match_list,
832                                    (GCompareFunc)cmp_offsets_reverse); 
833         return match_list;      
834 }
835
836
837
838 static void
839 hyperlinkify_plain_text (GString *txt)
840 {
841         GSList *cursor;
842         GSList *match_list = get_url_matches (txt);
843
844         /* we will work backwards, so the offsets stay valid */
845         for (cursor = match_list; cursor; cursor = cursor->next) {
846
847                 url_match_t *match = (url_match_t*) cursor->data;
848                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
849                 gchar *repl = NULL; /* replacement  */
850
851                 /* the prefix is NULL: use the one that is already there */
852                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
853                                         match->prefix ? match->prefix : EMPTY_STRING, 
854                                         url, url);
855
856                 /* replace the old thing with our hyperlink
857                  * replacement thing */
858                 g_string_erase  (txt, match->offset, match->len);
859                 g_string_insert (txt, match->offset, repl);
860                 
861                 g_free (url);
862                 g_free (repl);
863
864                 g_slice_free (url_match_t, match);      
865         }
866         
867         g_slist_free (match_list);
868 }
869
870
871
872 gchar*
873 modest_text_utils_get_display_address (gchar *address)
874 {
875         gchar *cursor;
876         
877         if (!address)
878                 return NULL;
879         
880         g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL);
881         
882         g_strchug (address); /* remove leading whitespace */
883
884         /*  <email@address> from display name */
885         cursor = g_strstr_len (address, strlen(address), "<");
886         if (cursor == address) /* there's nothing else? leave it */
887                 return address;
888         if (cursor) 
889                 cursor[0]='\0';
890
891         /* remove (bla bla) from display name */
892         cursor = g_strstr_len (address, strlen(address), "(");
893         if (cursor == address) /* there's nothing else? leave it */
894                 return address;
895         if (cursor) 
896                 cursor[0]='\0';
897
898         g_strchomp (address); /* remove trailing whitespace */
899
900         return address;
901 }
902
903
904
905 gint 
906 modest_text_utils_get_subject_prefix_len (const gchar *sub)
907 {
908         gint i;
909         static const gchar* prefix[] = {
910                 "Re:", "RE:", "Fwd:", "FWD:", "FW:", NULL
911         };
912                 
913         if (!sub || (sub[0] != 'R' && sub[0] != 'F')) /* optimization */
914                 return 0;
915
916         i = 0;
917         
918         while (prefix[i]) {
919                 if (g_str_has_prefix(sub, prefix[i])) {
920                         int prefix_len = strlen(prefix[i]); 
921                         if (sub[prefix_len] == ' ')
922                                 ++prefix_len; /* ignore space after prefix as well */
923                         return prefix_len; 
924                 }
925                 ++i;
926         }
927         return 0;
928 }
929
930
931 gint
932 modest_text_utils_utf8_strcmp (const gchar* s1, const gchar *s2, gboolean insensitive)
933 {
934         gint result = 0;
935         gchar *n1, *n2;
936
937         /* work even when s1 and/or s2 == NULL */
938         if (G_UNLIKELY(s1 == s2))
939                 return 0;
940
941         /* if it's not case sensitive */
942         if (!insensitive)
943                 return strcmp (s1 ? s1 : "", s2 ? s2 : "");
944         
945         n1 = g_utf8_collate_key (s1 ? s1 : "", -1);
946         n2 = g_utf8_collate_key (s2 ? s2 : "", -1);
947         
948         result = strcmp (n1, n2);
949
950         g_free (n1);
951         g_free (n2);
952         
953         return result;
954 }
955
956
957 gchar*
958 modest_text_utils_get_display_date (time_t date)
959 {
960         time_t now;
961         const guint BUF_SIZE = 64; 
962         gchar date_buf[BUF_SIZE];  
963         gchar now_buf [BUF_SIZE];  
964         
965         now = time (NULL);
966
967         modest_text_utils_strftime (date_buf, BUF_SIZE, "%d/%m/%Y", date);
968         modest_text_utils_strftime (now_buf,  BUF_SIZE, "%d/%m/%Y",  now); /* today */
969 /*      modest_text_utils_strftime (date_buf, BUF_SIZE, "%x", date); */
970 /*      modest_text_utils_strftime (now_buf,  BUF_SIZE, "%x",  now); /\* today *\/ */
971         
972         /* if this is today, get the time instead of the date */
973         if (strcmp (date_buf, now_buf) == 0)
974                 modest_text_utils_strftime (date_buf, BUF_SIZE, "%H:%M %P", date);
975         
976         return g_strdup(date_buf);
977 }
978
979 gboolean 
980 modest_text_utils_validate_email_address (const gchar *email_address)
981 {
982         int count = 0;
983         const gchar *c = NULL, *domain = NULL;
984         static gchar *rfc822_specials = "()<>@,;:\\\"[]";
985
986         /* first we validate the name portion (name@domain) */
987         for (c = email_address;  *c;  c++) {
988                 if (*c == '\"' && 
989                     (c == email_address || 
990                      *(c - 1) == '.' || 
991                      *(c - 1) == '\"')) {
992                         while (*++c) {
993                                 if (*c == '\"') 
994                                         break;
995                                 if (*c == '\\' && (*++c == ' ')) 
996                                         continue;
997                                 if (*c <= ' ' || *c >= 127) 
998                                         return FALSE;
999                         }
1000                         if (!*c++) 
1001                                 return FALSE;
1002                         if (*c == '@') 
1003                                 break;
1004                         if (*c != '.') 
1005                                 return FALSE;
1006                         continue;
1007                 }
1008                 if (*c == '@') 
1009                         break;
1010                 if (*c <= ' ' || *c >= 127) 
1011                         return FALSE;
1012                 if (strchr(rfc822_specials, *c)) 
1013                         return FALSE;
1014         }
1015         if (c == email_address || *(c - 1) == '.') 
1016                 return FALSE;
1017
1018         /* next we validate the domain portion (name@domain) */
1019         if (!*(domain = ++c)) 
1020                 return FALSE;
1021         do {
1022                 if (*c == '.') {
1023                         if (c == domain || *(c - 1) == '.') 
1024                                 return FALSE;
1025                         count++;
1026                 }
1027                 if (*c <= ' ' || *c >= 127) 
1028                         return FALSE;
1029                 if (strchr(rfc822_specials, *c)) 
1030                         return FALSE;
1031         } while (*++c);
1032
1033         return (count >= 1) ? TRUE : FALSE;
1034 }
1035
1036 gboolean 
1037 modest_text_utils_validate_recipient (const gchar *recipient)
1038 {
1039         gchar *stripped, *current;
1040         gchar *right_part;
1041         gboolean has_error = FALSE;
1042
1043         if (modest_text_utils_validate_email_address (recipient))
1044                 return TRUE;
1045         stripped = g_strdup (recipient);
1046         stripped = g_strstrip (stripped);
1047         current = stripped;
1048
1049         if (*current == '\0') {
1050                 g_free (stripped);
1051                 return FALSE;
1052         }
1053
1054         /* quoted string */
1055         if (*current == '\"') {
1056                 current = g_utf8_next_char (current);
1057                 has_error = TRUE;
1058                 for (; *current != '\0'; current = g_utf8_next_char (current)) {
1059                         if (*current == '\\') {
1060                                 /* TODO: This causes a warning, which breaks the build, 
1061                                  * because a gchar cannot be < 0.
1062                                  * murrayc. 
1063                                 if (current[1] <0) {
1064                                         has_error = TRUE;
1065                                         break;
1066                                 }
1067                                 */
1068                         } else if (*current == '\"') {
1069                                 has_error = FALSE;
1070                                 current = g_utf8_next_char (current);
1071                                 break;
1072                         }
1073                 }
1074         } else {
1075                 has_error = TRUE;
1076                 for (current = stripped ; *current != '\0'; current = g_utf8_next_char (current)) {
1077                         if (*current == '<') {
1078                                 has_error = FALSE;
1079                                 break;
1080                         }
1081                 }
1082         }
1083                 
1084         if (has_error) {
1085                 g_free (stripped);
1086                 return FALSE;
1087         }
1088
1089         right_part = g_strdup (current);
1090         g_free (stripped);
1091         right_part = g_strstrip (right_part);
1092
1093         if (g_str_has_prefix (right_part, "<") &&
1094             g_str_has_suffix (right_part, ">")) {
1095                 gchar *address;
1096                 gboolean valid;
1097
1098                 address = g_strndup (right_part+1, strlen (right_part) - 2);
1099                 g_free (right_part);
1100                 valid = modest_text_utils_validate_email_address (address);
1101                 g_free (address);
1102                 return valid;
1103         } else {
1104                 g_free (right_part);
1105                 return FALSE;
1106         }
1107 }
1108
1109
1110 gchar *
1111 modest_text_utils_get_display_size (guint64 size)
1112 {
1113         const guint KB=1024;
1114         const guint MB=1024 * KB;
1115         const guint GB=1024 * MB;
1116
1117         if (size == 0)
1118                 return g_strdup_printf(_FM("sfil_li_size_kb"), 0);
1119         if (0 < size && size < KB)
1120                 return g_strdup_printf (_FM("sfil_li_size_kb"), 1);
1121         else if (KB <= size && size < 100 * KB)
1122                 return g_strdup_printf (_FM("sfil_li_size_1kb_99kb"), size / KB);
1123         else if (100*KB <= size && size < MB)
1124                 return g_strdup_printf (_FM("sfil_li_size_100kb_1mb"), size / MB);
1125         else if (MB <= size && size < 10*MB)
1126                 return g_strdup_printf (_FM("sfil_li_size_1mb_10mb"), size / MB);
1127         else if (10*MB <= size && size < GB)
1128                 return g_strdup_printf (_FM("sfil_li_size_10mb_1gb"), size / MB);
1129         else
1130                 return g_strdup_printf (_FM("sfil_li_size_1gb_or_greater"), size / GB); 
1131 }