* src/modest-text-utils.[ch]:
[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");
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
385 GSList *
386 modest_text_utils_split_addresses_list (const gchar *addresses)
387 {
388         gchar *current, *start, *last_blank;
389         GSList *result = NULL;
390
391         start = (gchar *) addresses;
392         current = start;
393         last_blank = start;
394
395         while (*current != '\0') {
396                 if ((start == current)&&((*current == ' ')||(*current == ','))) {
397                         start++;
398                         last_blank = current;
399                 } else if (*current == ',') {
400                         gchar *new_address = NULL;
401                         new_address = g_strndup (start, current - last_blank);
402                         result = g_slist_prepend (result, new_address);
403                         start = current + 1;
404                         last_blank = start;
405                 } else if (*current == '\"') {
406                         if (current == start) {
407                                 current++;
408                                 start++;
409                         }
410                         while ((*current != '\"')&&(*current != '\0'))
411                                 current++;
412                 }
413                                 
414                 current++;
415         }
416
417         if (start != current) {
418                 gchar *new_address = NULL;
419                 new_address = g_strndup (start, current - last_blank);
420                 result = g_slist_prepend (result, new_address);
421         }
422
423         result = g_slist_reverse (result);
424         return result;
425
426 }
427
428 void
429 modest_text_utils_address_range_at_position (const gchar *recipients_list,
430                                              gint position,
431                                              gint *start,
432                                              gint *end)
433 {
434         gchar *current = NULL;
435         gint range_start = 0;
436         gint range_end = 0;
437         gint index;
438         gboolean is_quoted = FALSE;
439
440         index = 0;
441         for (current = (gchar *) recipients_list; *current != '\0'; current = g_utf8_find_next_char (current, NULL)) {
442                 gunichar c = g_utf8_get_char (current);
443
444                 if ((c == ',') && (!is_quoted)) {
445                         if (index < position) {
446                                 range_start = index + 1;
447                         } else {
448                                 break;
449                         }
450                 } else if (c == '\"') {
451                         is_quoted = !is_quoted;
452                 } else if ((c == ' ') &&(range_start == index)) {
453                         range_start ++;
454                 }
455                 index ++;
456                 range_end = index;
457         }
458
459         if (start)
460                 *start = range_start;
461         if (end)
462                 *end = range_end;
463 }
464
465
466 /* ******************************************************************* */
467 /* ************************* UTILIY FUNCTIONS ************************ */
468 /* ******************************************************************* */
469
470 static GString *
471 get_next_line (const gchar * b, const gsize blen, const gchar * iter)
472 {
473         GString *gs;
474         const gchar *i0;
475         
476         if (iter > b + blen)
477                 return g_string_new("");
478         
479         i0 = iter;
480         while (iter[0]) {
481                 if (iter[0] == '\n')
482                         break;
483                 iter++;
484         }
485         gs = g_string_new_len (i0, iter - i0);
486         return gs;
487 }
488 static int
489 get_indent_level (const char *l)
490 {
491         int indent = 0;
492
493         while (l[0]) {
494                 if (l[0] == '>') {
495                         indent++;
496                         if (l[1] == ' ') {
497                                 l++;
498                         }
499                 } else {
500                         break;
501                 }
502                 l++;
503
504         }
505
506         /*      if we hit the signature marker "-- ", we return -(indent + 1). This
507          *      stops reformatting.
508          */
509         if (strcmp (l, "-- ") == 0) {
510                 return -1 - indent;
511         } else {
512                 return indent;
513         }
514 }
515
516 static void
517 unquote_line (GString * l)
518 {
519         gchar *p;
520
521         p = l->str;
522         while (p[0]) {
523                 if (p[0] == '>') {
524                         if (p[1] == ' ') {
525                                 p++;
526                         }
527                 } else {
528                         break;
529                 }
530                 p++;
531         }
532         g_string_erase (l, 0, p - l->str);
533 }
534
535 static void
536 append_quoted (GString * buf, int indent, const GString * str,
537                const int cutpoint)
538 {
539         int i;
540
541         indent = indent < 0 ? abs (indent) - 1 : indent;
542         for (i = 0; i <= indent; i++) {
543                 g_string_append (buf, "> ");
544         }
545         if (cutpoint > 0) {
546                 g_string_append_len (buf, str->str, cutpoint);
547         } else {
548                 g_string_append (buf, str->str);
549         }
550         g_string_append (buf, "\n");
551 }
552
553 static int
554 get_breakpoint_utf8 (const gchar * s, gint indent, const gint limit)
555 {
556         gint index = 0;
557         const gchar *pos, *last;
558         gunichar *uni;
559
560         indent = indent < 0 ? abs (indent) - 1 : indent;
561
562         last = NULL;
563         pos = s;
564         uni = g_utf8_to_ucs4_fast (s, -1, NULL);
565         while (pos[0]) {
566                 if ((index + 2 * indent > limit) && last) {
567                         g_free (uni);
568                         return last - s;
569                 }
570                 if (g_unichar_isspace (uni[index])) {
571                         last = pos;
572                 }
573                 pos = g_utf8_next_char (pos);
574                 index++;
575         }
576         g_free (uni);
577         return strlen (s);
578 }
579
580 static int
581 get_breakpoint_ascii (const gchar * s, const gint indent, const gint limit)
582 {
583         gint i, last;
584
585         last = strlen (s);
586         if (last + 2 * indent < limit)
587                 return last;
588
589         for (i = strlen (s); i > 0; i--) {
590                 if (s[i] == ' ') {
591                         if (i + 2 * indent <= limit) {
592                                 return i;
593                         } else {
594                                 last = i;
595                         }
596                 }
597         }
598         return last;
599 }
600
601 static int
602 get_breakpoint (const gchar * s, const gint indent, const gint limit)
603 {
604
605         if (g_utf8_validate (s, -1, NULL)) {
606                 return get_breakpoint_utf8 (s, indent, limit);
607         } else {                /* assume ASCII */
608                 //g_warning("invalid UTF-8 in msg");
609                 return get_breakpoint_ascii (s, indent, limit);
610         }
611 }
612
613 static gchar *
614 cite (const time_t sent_date, const gchar *from)
615 {
616         gchar sent_str[101];
617
618         /* format sent_date */
619         modest_text_utils_strftime (sent_str, 100, "%c", sent_date);
620         return g_strdup_printf (N_("On %s, %s wrote:\n"), 
621                                 sent_str, 
622                                 (from) ? from : EMPTY_STRING);
623 }
624
625
626 static gchar *
627 modest_text_utils_quote_plain_text (const gchar *text, 
628                                     const gchar *cite, 
629                                     int limit)
630 {
631         const gchar *iter;
632         gint indent, breakpoint, rem_indent = 0;
633         GString *q, *l, *remaining;
634         gsize len;
635
636         /* remaining will store the rest of the line if we have to break it */
637         q = g_string_new (cite);
638         remaining = g_string_new ("");
639
640         iter = text;
641         len = strlen(text);
642         do {
643                 l = get_next_line (text, len, iter);
644                 iter = iter + l->len + 1;
645                 indent = get_indent_level (l->str);
646                 unquote_line (l);
647
648                 if (remaining->len) {
649                         if (l->len && indent == rem_indent) {
650                                 g_string_prepend (l, " ");
651                                 g_string_prepend (l, remaining->str);
652                         } else {
653                                 do {
654                                         breakpoint =
655                                                 get_breakpoint (remaining->str,
656                                                                 rem_indent,
657                                                                 limit);
658                                         append_quoted (q, rem_indent,
659                                                        remaining, breakpoint);
660                                         g_string_erase (remaining, 0,
661                                                         breakpoint);
662                                         if (remaining->str[0] == ' ') {
663                                                 g_string_erase (remaining, 0,
664                                                                 1);
665                                         }
666                                 } while (remaining->len);
667                         }
668                 }
669                 g_string_free (remaining, TRUE);
670                 breakpoint = get_breakpoint (l->str, indent, limit);
671                 remaining = g_string_new (l->str + breakpoint);
672                 if (remaining->str[0] == ' ') {
673                         g_string_erase (remaining, 0, 1);
674                 }
675                 rem_indent = indent;
676                 append_quoted (q, indent, l, breakpoint);
677                 g_string_free (l, TRUE);
678         } while ((iter < text + len) || (remaining->str[0]));
679
680         return g_string_free (q, FALSE);
681 }
682
683 static gchar*
684 modest_text_utils_quote_html (const gchar *text, 
685                               const gchar *cite, 
686                               int limit)
687 {
688         const gchar *format = \
689                 "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" \
690                 "<html>\n" \
691                 "<body>\n" \
692                 "%s" \
693                 "<blockquote type=\"cite\">\n%s\n</blockquote>\n" \
694                 "</body>\n" \
695                 "</html>\n";
696
697         return g_strdup_printf (format, cite, text);
698 }
699
700 static gint 
701 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
702 {
703         return match2->offset - match1->offset;
704 }
705
706
707
708 /*
709  * check if the match is inside an existing match... */
710 static void
711 chk_partial_match (const url_match_t *match, guint* offset)
712 {
713         if (*offset >= match->offset && *offset < match->offset + match->len)
714                 *offset = -1;
715 }
716
717 static GSList*
718 get_url_matches (GString *txt)
719 {
720         regmatch_t rm;
721         guint rv, i, offset = 0;
722         GSList *match_list = NULL;
723
724         static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
725         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
726
727         /* initalize the regexps */
728         for (i = 0; i != pattern_num; ++i) {
729                 patterns[i].preg = g_slice_new0 (regex_t);
730
731                 /* this should not happen */
732                 g_return_val_if_fail (regcomp (patterns[i].preg, patterns[i].regex,
733                                                REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0, NULL);
734         }
735         /* find all the matches */
736         for (i = 0; i != pattern_num; ++i) {
737                 offset     = 0; 
738                 while (1) {
739                         int test_offset;
740                         if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) {
741                                 g_return_val_if_fail (rv == REG_NOMATCH, NULL); /* this should not happen */
742                                 break; /* try next regexp */ 
743                         }
744                         if (rm.rm_so == -1)
745                                 break;
746
747                         /* FIXME: optimize this */
748                         /* to avoid partial matches on something that was already found... */
749                         /* check_partial_match will put -1 in the data ptr if that is the case */
750                         test_offset = offset + rm.rm_so;
751                         g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset);
752                         
753                         /* make a list of our matches (<offset, len, prefix> tupels)*/
754                         if (test_offset != -1) {
755                                 url_match_t *match = g_slice_new (url_match_t);
756                                 match->offset = offset + rm.rm_so;
757                                 match->len    = rm.rm_eo - rm.rm_so;
758                                 match->prefix = patterns[i].prefix;
759                                 match_list = g_slist_prepend (match_list, match);
760                         }
761                         offset += rm.rm_eo;
762                 }
763         }
764
765         for (i = 0; i != pattern_num; ++i) {
766                 regfree (patterns[i].preg);
767                 g_slice_free  (regex_t, patterns[i].preg);
768         } /* don't free patterns itself -- it's static */
769         
770         /* now sort the list, so the matches are in reverse order of occurence.
771          * that way, we can do the replacements starting from the end, so we don't need
772          * to recalculate the offsets
773          */
774         match_list = g_slist_sort (match_list,
775                                    (GCompareFunc)cmp_offsets_reverse); 
776         return match_list;      
777 }
778
779
780
781 static void
782 hyperlinkify_plain_text (GString *txt)
783 {
784         GSList *cursor;
785         GSList *match_list = get_url_matches (txt);
786
787         /* we will work backwards, so the offsets stay valid */
788         for (cursor = match_list; cursor; cursor = cursor->next) {
789
790                 url_match_t *match = (url_match_t*) cursor->data;
791                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
792                 gchar *repl = NULL; /* replacement  */
793
794                 /* the prefix is NULL: use the one that is already there */
795                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
796                                         match->prefix ? match->prefix : EMPTY_STRING, 
797                                         url, url);
798
799                 /* replace the old thing with our hyperlink
800                  * replacement thing */
801                 g_string_erase  (txt, match->offset, match->len);
802                 g_string_insert (txt, match->offset, repl);
803                 
804                 g_free (url);
805                 g_free (repl);
806
807                 g_slice_free (url_match_t, match);      
808         }
809         
810         g_slist_free (match_list);
811 }
812
813
814
815 gchar*
816 modest_text_utils_get_display_address (gchar *address)
817 {
818         gchar *cursor;
819         
820         if (!address)
821                 return NULL;
822
823         g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL);
824
825         g_strchug (address); /* remove leading whitespace */
826
827         /*  <email@address> from display name */
828         cursor = g_strstr_len (address, strlen(address), "<");
829         if (cursor == address) /* there's nothing else? leave it */
830                 return address;
831         if (cursor) 
832                 cursor[0]='\0';
833
834         /* remove (bla bla) from display name */
835         cursor = g_strstr_len (address, strlen(address), "(");
836         if (cursor == address) /* there's nothing else? leave it */
837                 return address;
838         if (cursor) 
839                 cursor[0]='\0';
840
841         g_strchomp (address); /* remove trailing whitespace */
842
843         return address;
844 }
845
846
847
848 gint 
849 modest_text_utils_get_subject_prefix_len (const gchar *sub)
850 {
851         gint i;
852         static const gchar* prefix[] = {
853                 "Re:", "RE:", "Fwd:", "FWD:", "FW:", NULL
854         };
855                 
856         if (!sub || (sub[0] != 'R' && sub[0] != 'F')) /* optimization */
857                 return 0;
858
859         i = 0;
860         
861         while (prefix[i]) {
862                 if (g_str_has_prefix(sub, prefix[i])) {
863                         int prefix_len = strlen(prefix[i]); 
864                         if (sub[prefix_len] == ' ')
865                                 ++prefix_len; /* ignore space after prefix as well */
866                         return prefix_len; 
867                 }
868                 ++i;
869         }
870         return 0;
871 }
872
873
874 gint
875 modest_text_utils_utf8_strcmp (const gchar* s1, const gchar *s2, gboolean insensitive)
876 {
877         gint result = 0;
878         gchar *n1, *n2;
879
880         /* work even when s1 and/or s2 == NULL */
881         if (G_UNLIKELY(s1 == s2))
882                 return 0;
883
884         /* if it's not case sensitive */
885         if (!insensitive)
886                 return strcmp (s1 ? s1 : "", s2 ? s2 : "");
887         
888         n1 = g_utf8_collate_key (s1 ? s1 : "", -1);
889         n2 = g_utf8_collate_key (s2 ? s2 : "", -1);
890         
891         result = strcmp (n1, n2);
892
893         g_free (n1);
894         g_free (n2);
895         
896         return result;
897 }
898
899
900 gchar*
901 modest_text_utils_get_display_date (time_t date)
902 {
903         time_t now;
904         const guint BUF_SIZE = 64; 
905         gchar date_buf[BUF_SIZE];  
906         gchar now_buf [BUF_SIZE];  
907         
908         now = time (NULL);
909
910         modest_text_utils_strftime (date_buf, BUF_SIZE, "%d/%m/%Y", date);
911         modest_text_utils_strftime (now_buf,  BUF_SIZE, "%d/%m/%Y",  now); /* today */
912 /*      modest_text_utils_strftime (date_buf, BUF_SIZE, "%x", date); */
913 /*      modest_text_utils_strftime (now_buf,  BUF_SIZE, "%x",  now); /\* today *\/ */
914         
915         /* if this is today, get the time instead of the date */
916         if (strcmp (date_buf, now_buf) == 0)
917                 modest_text_utils_strftime (date_buf, BUF_SIZE, "%H:%M %P", date);
918         
919         return g_strdup(date_buf);
920 }
921
922 gboolean 
923 modest_text_utils_validate_email_address (const gchar *email_address)
924 {
925         int count = 0;
926         const gchar *c = NULL, *domain = NULL;
927         static gchar *rfc822_specials = "()<>@,;:\\\"[]";
928
929         /* first we validate the name portion (name@domain) */
930         for (c = email_address;  *c;  c++) {
931                 if (*c == '\"' && 
932                     (c == email_address || 
933                      *(c - 1) == '.' || 
934                      *(c - 1) == '\"')) {
935                         while (*++c) {
936                                 if (*c == '\"') 
937                                         break;
938                                 if (*c == '\\' && (*++c == ' ')) 
939                                         continue;
940                                 if (*c <= ' ' || *c >= 127) 
941                                         return FALSE;
942                         }
943                         if (!*c++) 
944                                 return FALSE;
945                         if (*c == '@') 
946                                 break;
947                         if (*c != '.') 
948                                 return FALSE;
949                         continue;
950                 }
951                 if (*c == '@') 
952                         break;
953                 if (*c <= ' ' || *c >= 127) 
954                         return FALSE;
955                 if (strchr(rfc822_specials, *c)) 
956                         return FALSE;
957         }
958         if (c == email_address || *(c - 1) == '.') 
959                 return FALSE;
960
961         /* next we validate the domain portion (name@domain) */
962         if (!*(domain = ++c)) 
963                 return FALSE;
964         do {
965                 if (*c == '.') {
966                         if (c == domain || *(c - 1) == '.') 
967                                 return FALSE;
968                         count++;
969                 }
970                 if (*c <= ' ' || *c >= 127) 
971                         return FALSE;
972                 if (strchr(rfc822_specials, *c)) 
973                         return FALSE;
974         } while (*++c);
975
976         return (count >= 1) ? TRUE : FALSE;
977 }
978
979 gboolean 
980 modest_text_utils_validate_recipient (const gchar *recipient)
981 {
982         gchar *stripped;
983         gchar *right_part;
984         gint i = 0;
985         gboolean has_error = FALSE;
986
987         if (modest_text_utils_validate_email_address (recipient))
988                 return TRUE;
989         stripped = g_strdup (recipient);
990         stripped = g_strstrip (stripped);
991
992         if (stripped[0] == '\0') {
993                 g_free (stripped);
994                 return FALSE;
995         }
996
997         /* quoted string */
998         if (stripped[0] == '\"') {
999                 i = 1;
1000                 has_error = TRUE;
1001                 for (i = 1; stripped[i] != '\0'; i++) {
1002                         if (stripped[i] == '\\') {
1003                                 if (stripped[i+1] >=0) {
1004                                         i++;
1005                                 } else {
1006                                         has_error = TRUE;
1007                                         break;
1008                                 }
1009                         } else if (stripped[i] == '\"') {
1010                                 has_error = FALSE;
1011                                 break;
1012                         }
1013                 }
1014         } else {
1015                 has_error = TRUE;
1016                 for (i = 0; stripped[i] != '\0'; i++) {
1017                         if (stripped[i] == ' ') {
1018                                 has_error = FALSE;
1019                                 break;
1020                         }
1021                 }
1022         }
1023                 
1024         if (has_error) {
1025                 g_free (stripped);
1026                 return FALSE;
1027         }
1028
1029         right_part = g_strdup (stripped + i);
1030         g_free (stripped);
1031         right_part = g_strstrip (right_part);
1032         if (g_str_has_prefix (right_part, "<") &&
1033             g_str_has_suffix (right_part, ">")) {
1034                 gchar *address;
1035                 gboolean valid;
1036
1037                 address = g_strndup (right_part+1, strlen (right_part) - 2);
1038                 g_free (right_part);
1039                 valid = modest_text_utils_validate_email_address (address);
1040                 g_free (address);
1041                 return valid;
1042         } else {
1043                 g_free (right_part);
1044                 return FALSE;
1045         }
1046 }
1047
1048
1049 gchar *
1050 modest_text_utils_get_display_size (guint size)
1051 {
1052         const guint KB=1024;
1053         const guint MB=1024 * KB;
1054         const guint GB=1024 * MB;
1055         const guint TB=1024 * GB;
1056
1057         if (size < KB)
1058                 return g_strdup_printf (_("%0.1f Kb"), (double)size / KB);
1059         else if (size < MB)
1060                 return g_strdup_printf (_("%d Kb"), size / KB);
1061         else if (size < GB)
1062                 return g_strdup_printf (_("%d Mb"), size / MB);
1063         else if (size < TB)
1064                 return g_strdup_printf (_("%d Gb"), size/ GB);
1065         else
1066                 return g_strdup_printf (_("Very big"));
1067 }