Use non-localized reply and forward prefixes
[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
32 #ifndef _GNU_SOURCE
33 #define _GNU_SOURCE
34 #endif /*_GNU_SOURCE*/
35 #include <string.h> /* for strcasestr */
36
37
38 #include <glib.h>
39 #include <stdlib.h>
40 #include <glib/gi18n.h>
41 #include <regex.h>
42 #include <modest-tny-platform-factory.h>
43 #include <modest-text-utils.h>
44 #include <modest-runtime.h>
45 #include <ctype.h>
46
47 #ifdef HAVE_CONFIG_H
48 #include <config.h>
49 #endif /*HAVE_CONFIG_H */
50
51 /* defines */
52 #define FORWARD_STRING _("mcen_ia_editor_original_message")
53 #define FROM_STRING _("mail_va_from")
54 #define SENT_STRING _("mcen_fi_message_properties_sent")
55 #define TO_STRING _("mail_va_to")
56 #define SUBJECT_STRING _("mail_va_subject")
57 #define EMPTY_STRING ""
58
59 /*
60  * do the hyperlinkification only for texts < 50 Kb,
61  * as it's quite slow. Without this, e.g. mail with
62  * an uuencoded part (which is not recognized as attachment,
63  * will hang modest
64  */
65 #define HYPERLINKIFY_MAX_LENGTH (1024*50)
66
67 /*
68  * we need these regexps to find URLs in plain text e-mails
69  */
70 typedef struct _url_match_pattern_t url_match_pattern_t;
71 struct _url_match_pattern_t {
72         gchar   *regex;
73         regex_t *preg;
74         gchar   *prefix;
75 };
76
77 typedef struct _url_match_t url_match_t;
78 struct _url_match_t {
79         guint offset;
80         guint len;
81         const gchar* prefix;
82 };
83
84
85 /*
86  * we mark the ampersand with \007 when converting text->html
87  * because after text->html we do hyperlink detecting, which
88  * could be screwed up by the ampersand.
89  * ie. 1<3 ==> 1\007lt;3
90  */
91 #define MARK_AMP '\007'
92 #define MARK_AMP_STR "\007"
93
94 /* mark &amp; separately, because they are parts of urls.
95  * ie. a&b => a\006amp;b, but a>b => a\007gt;b
96  *
97  * we need to handle '&' separately, because it can be part of URIs
98  * (as in href="http://foo.bar?a=1&b=1"), so inside those URIs
99  * we need to re-replace \006amp; with '&' again, while outside uri's
100  * it will be '&amp;'
101  * 
102  * yes, it's messy, but a consequence of doing text->html first, then hyperlinkify
103  */
104 #define MARK_AMP_URI '\006'
105 #define MARK_AMP_URI_STR "\006"
106
107
108 /* note: match MARK_AMP_URI_STR as well, because after txt->html, a '&' will look like $(MARK_AMP_URI_STR)"amp;" */
109 #define MAIL_VIEWER_URL_MATCH_PATTERNS  {                               \
110         { "(feed:|)(file|rtsp|http|ftp|https|mms|mmsh|webcal|feed|rtsp|rdp|lastfm|sip)://[-a-z0-9_$.+!*(),;:@%=\?/~#&" MARK_AMP_URI_STR \
111                         "]+[-a-z0-9_$%&" MARK_AMP_URI_STR "=?/~#]",     \
112           NULL, NULL },\
113         { "www\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",\
114                         NULL, "http://" },                              \
115         { "ftp\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",\
116           NULL, "ftp://" },\
117         { "(jabberto|voipto|sipto|sip|chatto|skype|xmpp):[-_a-z@0-9.+]+", \
118            NULL, NULL},                                             \
119         { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+",                    \
120           NULL, NULL},\
121         { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\
122           NULL, "mailto:"}\
123         }
124
125 const gchar account_title_forbidden_chars[] = {
126         '\\', '/', ':', '*', '?', '\'', '<', '>', '|', '^'
127 };
128 const gchar folder_name_forbidden_chars[] = {
129         '<', '>', ':', '\'', '/', '\\', '|', '?', '*', '^', '%', '$', '#', '&'
130 };
131 const gchar user_name_forbidden_chars[] = {
132         '<', '>'
133 };
134 const guint ACCOUNT_TITLE_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (account_title_forbidden_chars);
135 const guint FOLDER_NAME_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (folder_name_forbidden_chars);
136 const guint USER_NAME_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (user_name_forbidden_chars);
137
138 /* private */
139 static gchar*   cite                    (const time_t sent_date, const gchar *from);
140 static void     hyperlinkify_plain_text (GString *txt, gint offset);
141 static gint     cmp_offsets_reverse     (const url_match_t *match1, const url_match_t *match2);
142 static GSList*  get_url_matches         (GString *txt, gint offset);
143
144 static GString* get_next_line           (const char *b, const gsize blen, const gchar * iter);
145 static int      get_indent_level        (const char *l);
146 static void     unquote_line            (GString * l, const gchar *quote_symbol);
147 static void     append_quoted           (GString * buf, const gchar *quote_symbol,
148                                          const int indent, const GString * str, 
149                                          const int cutpoint);
150 static int      get_breakpoint_utf8     (const gchar * s, const gint indent, const gint limit);
151 static int      get_breakpoint_ascii    (const gchar * s, const gint indent, const gint limit);
152 static int      get_breakpoint          (const gchar * s, const gint indent, const gint limit);
153
154 static gchar*   modest_text_utils_quote_plain_text (const gchar *text, 
155                                                     const gchar *cite, 
156                                                     const gchar *signature,
157                                                     GList *attachments, 
158                                                     int limit);
159
160 static gchar*   modest_text_utils_quote_html       (const gchar *text, 
161                                                     const gchar *cite,
162                                                     const gchar *signature,
163                                                     GList *attachments,
164                                                     int limit);
165 static gchar*   get_email_from_address (const gchar *address);
166 static void     remove_extra_spaces (gchar *string);
167
168
169
170 /* ******************************************************************* */
171 /* ************************* PUBLIC FUNCTIONS ************************ */
172 /* ******************************************************************* */
173
174 gchar *
175 modest_text_utils_quote (const gchar *text, 
176                          const gchar *content_type,
177                          const gchar *signature,
178                          const gchar *from,
179                          const time_t sent_date, 
180                          GList *attachments,
181                          int limit)
182 {
183         gchar *retval, *cited;
184
185         g_return_val_if_fail (text, NULL);
186         g_return_val_if_fail (content_type, NULL);
187
188         cited = cite (sent_date, from);
189         
190         if (content_type && strcmp (content_type, "text/html") == 0)
191                 /* TODO: extract the <body> of the HTML and pass it to
192                    the function */
193                 retval = modest_text_utils_quote_html (text, cited, signature, attachments, limit);
194         else
195                 retval = modest_text_utils_quote_plain_text (text, cited, signature, attachments, limit);
196         
197         g_free (cited);
198         
199         return retval;
200 }
201
202
203 gchar *
204 modest_text_utils_cite (const gchar *text,
205                         const gchar *content_type,
206                         const gchar *signature,
207                         const gchar *from,
208                         time_t sent_date)
209 {
210         gchar *retval;
211         gchar *tmp_sig;
212         
213         g_return_val_if_fail (text, NULL);
214         g_return_val_if_fail (content_type, NULL);
215         
216         if (!signature) {
217                 tmp_sig = g_strdup (text);
218         } else {
219                 tmp_sig = g_strconcat (text, "\n", MODEST_TEXT_UTILS_SIGNATURE_MARKER, "\n", signature, NULL);
220         }
221
222         if (strcmp (content_type, "text/html") == 0) {
223                 retval = modest_text_utils_convert_to_html_body (tmp_sig, -1, TRUE);
224                 g_free (tmp_sig);
225         } else {
226                 retval = tmp_sig;
227         }
228
229         return retval;
230 }
231
232 static gchar *
233 forward_cite (const gchar *from,
234               const gchar *sent,
235               const gchar *to,
236               const gchar *subject)
237 {
238         g_return_val_if_fail (sent, NULL);
239         
240         return g_strdup_printf ("%s\n%s %s\n%s %s\n%s %s\n%s %s\n", 
241                                 FORWARD_STRING, 
242                                 FROM_STRING, (from)?from:"",
243                                 SENT_STRING, sent,
244                                 TO_STRING, (to)?to:"",
245                                 SUBJECT_STRING, (subject)?subject:"");
246 }
247
248 gchar * 
249 modest_text_utils_inline (const gchar *text,
250                           const gchar *content_type,
251                           const gchar *signature,
252                           const gchar *from,
253                           time_t sent_date,
254                           const gchar *to,
255                           const gchar *subject)
256 {
257         gchar sent_str[101];
258         gchar *cited;
259         gchar *retval;
260         
261         g_return_val_if_fail (text, NULL);
262         g_return_val_if_fail (content_type, NULL);
263         
264         modest_text_utils_strftime (sent_str, 100, "%c", sent_date);
265
266         cited = forward_cite (from, sent_str, to, subject);
267         
268         if (content_type && strcmp (content_type, "text/html") == 0)
269                 retval = modest_text_utils_quote_html (text, cited, signature, NULL, 80);
270         else
271                 retval = modest_text_utils_quote_plain_text (text, cited, signature, NULL, 80);
272         
273         g_free (cited);
274         return retval;
275 }
276
277 /* just to prevent warnings:
278  * warning: `%x' yields only last 2 digits of year in some locales
279  */
280 gsize
281 modest_text_utils_strftime(char *s, gsize max, const char *fmt, time_t timet)
282 {
283         struct tm tm;
284
285         /* To prevent possible problems in strftime that could leave
286            garbage in the s variable */
287         if (s)
288                 s[0] = '\0';
289         else
290                 return 0;
291
292         /* does not work on old maemo glib: 
293          *   g_date_set_time_t (&date, timet);
294          */
295         localtime_r (&timet, &tm);
296         return strftime(s, max, fmt, &tm);
297 }
298
299 gchar *
300 modest_text_utils_derived_subject (const gchar *subject, gboolean is_reply)
301 {
302         gchar *tmp, *subject_dup, *retval, *prefix;
303         const gchar *untranslated_prefix;
304         gint prefix_len, untranslated_prefix_len;
305         gboolean found = FALSE;
306
307         if (!subject || subject[0] == '\0')
308                 subject = _("mail_va_no_subject");
309
310         subject_dup = g_strdup (subject);
311         tmp = g_strchug (subject_dup);
312
313         prefix = (is_reply) ? _("mail_va_re") : _("mail_va_fw");
314         prefix = g_strconcat (prefix, ":", NULL);
315         prefix_len = g_utf8_strlen (prefix, -1);
316
317         untranslated_prefix =  (is_reply) ? "Re:" : "Fw:";
318         untranslated_prefix_len = 3;
319
320         if (g_str_has_prefix (tmp, prefix) ||
321             g_str_has_prefix (tmp, untranslated_prefix)) {
322                 found = TRUE;
323         } else {
324                 gchar *prefix_down, *tmp_down, *untranslated_down;
325
326                 prefix_down = g_utf8_strdown (prefix, -1);
327                 untranslated_down = g_utf8_strdown (untranslated_prefix, -1);
328                 tmp_down = g_utf8_strdown (tmp, -1);
329                 if (g_str_has_prefix (tmp_down, prefix_down) ||
330                     g_str_has_prefix (tmp_down, untranslated_down) ||
331                     (!is_reply && g_str_has_prefix (tmp_down, "fwd:")))
332                         found = TRUE;
333
334                 g_free (prefix_down);
335                 g_free (untranslated_down);
336                 g_free (tmp_down);
337         }
338
339         if (found) {
340                 /* If the prefix is already present do not touch the subject */
341                 retval = subject_dup;
342         } else {
343                 /* Normal case, add the prefix */
344                 retval = g_strdup_printf ("%s %s", untranslated_prefix, tmp);
345                 g_free (subject_dup);
346         }
347         g_free (prefix);
348
349         return retval;
350 }
351
352
353 /* Performs a case-insensitive strstr for ASCII strings */
354 static const gchar *
355 ascii_stristr(const gchar *haystack, const gchar *needle)
356 {
357         int needle_len;
358         int haystack_len;
359         const gchar *pos;
360         const gchar *max_pos;
361
362         if (haystack == NULL || needle == NULL) {
363                 return haystack;  /* as in strstr */
364         }
365
366         needle_len = strlen(needle);
367
368         if (needle_len == 0) {
369                 return haystack;  /* as in strstr */
370         }
371
372         haystack_len = strlen(haystack);
373         max_pos = haystack + haystack_len - needle_len;
374
375         for (pos = haystack; pos <= max_pos; pos++) {
376                 if (g_ascii_strncasecmp (pos, needle, needle_len) == 0) {
377                         return pos;
378                 }
379         }
380
381         return NULL;
382 }
383
384
385 gchar*
386 modest_text_utils_remove_address (const gchar *address_list, const gchar *address)
387 {
388         gchar *dup, *token, *ptr = NULL, *result;
389         GString *filtered_emails;
390         gchar *email_address;
391
392         g_return_val_if_fail (address_list, NULL);
393
394         if (!address)
395                 return g_strdup (address_list);
396
397         email_address = get_email_from_address (address);
398
399         /* search for substring */
400         if (!ascii_stristr ((const char *) address_list, (const char *) email_address)) {
401                 g_free (email_address);
402                 return g_strdup (address_list);
403         }
404
405         dup = g_strdup (address_list);
406         filtered_emails = g_string_new (NULL);
407
408         token = strtok_r (dup, ",", &ptr);
409
410         while (token != NULL) {
411                 /* Add to list if not found */
412                 if (!ascii_stristr ((const char *) token, (const char *) email_address)) {
413                         if (filtered_emails->len == 0)
414                                 g_string_append_printf (filtered_emails, "%s", g_strstrip (token));
415                         else
416                                 g_string_append_printf (filtered_emails, ",%s", g_strstrip (token));
417                 }
418                 token = strtok_r (NULL, ",", &ptr);
419         }
420         result = filtered_emails->str;
421
422         /* Clean */
423         g_free (email_address);
424         g_free (dup);
425         g_string_free (filtered_emails, FALSE);
426
427         return result;
428 }
429
430
431 gchar*
432 modest_text_utils_remove_duplicate_addresses (const gchar *address_list)
433 {
434         GSList *addresses, *cursor;
435         GHashTable *table;
436         gchar *new_list = NULL;
437         
438         g_return_val_if_fail (address_list, NULL);
439
440         table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
441         addresses = modest_text_utils_split_addresses_list (address_list);
442
443         cursor = addresses;
444         while (cursor) {
445                 const gchar* address = (const gchar*)cursor->data;
446
447                 /* We need only the email to just compare it and not
448                    the full address which would make "a <a@a.com>"
449                    different from "a@a.com" */
450                 const gchar *email = get_email_from_address (address);
451
452                 /* ignore the address if already seen */
453                 if (g_hash_table_lookup (table, email) == 0) {
454                         gchar *tmp;
455
456                         /* Include the full address and not only the
457                            email in the returned list */
458                         if (!new_list) {
459                                 tmp = g_strdup (address);
460                         } else {
461                                 tmp = g_strjoin (",", new_list, address, NULL);
462                                 g_free (new_list);
463                         }
464                         new_list = tmp;
465                         
466                         g_hash_table_insert (table, (gchar*)email, GINT_TO_POINTER(1));
467                 }
468                 cursor = g_slist_next (cursor);
469         }
470
471         g_hash_table_unref (table);
472         g_slist_foreach (addresses, (GFunc)g_free, NULL);
473         g_slist_free (addresses);
474
475         if (new_list == NULL)
476                 new_list = g_strdup ("");
477
478         return new_list;
479 }
480
481
482 static void
483 modest_text_utils_convert_buffer_to_html_start (GString *html, const gchar *data, gssize n)
484 {
485         guint           i;
486         gboolean        space_seen = FALSE;
487         guint           break_dist = 0; /* distance since last break point */
488
489         if (n == -1)
490                 n = strlen (data);
491
492         /* replace with special html chars where needed*/
493         for (i = 0; i != n; ++i)  {
494                 guchar kar = data[i];
495                 
496                 if (space_seen && kar != ' ') {
497                         g_string_append (html, "&#32;");
498                         space_seen = FALSE;
499                 }
500                 
501                 /* we artificially insert a breakpoint (newline)
502                  * after 256, to make sure our lines are not so long
503                  * they will DOS the regexping later
504                  * Also, check that kar is ASCII to make sure that we
505                  * don't break a UTF8 char in two
506                  */
507                 if (++break_dist >= 256 && kar < 127) {
508                         g_string_append_c (html, '\n');
509                         break_dist = 0;
510                 }
511                 
512                 switch (kar) {
513                 case 0:
514                 case MARK_AMP:
515                 case MARK_AMP_URI:      
516                         /* this is a temp place holder for '&'; we can only
517                                 * set the real '&' after hyperlink translation, otherwise
518                                 * we might screw that up */
519                         break; /* ignore embedded \0s and MARK_AMP */   
520                 case '<'  : g_string_append (html, MARK_AMP_STR "lt;");   break;
521                 case '>'  : g_string_append (html, MARK_AMP_STR "gt;");   break;
522                 case '&'  : g_string_append (html, MARK_AMP_URI_STR "amp;");  break; /* special case */
523                 case '"'  : g_string_append (html, MARK_AMP_STR "quot;");  break;
524
525                 /* don't convert &apos; --> wpeditor will try to re-convert it... */    
526                 //case '\'' : g_string_append (html, "&apos;"); break;
527                 case '\n' : g_string_append (html, "<br>\n");break_dist= 0; break;
528                 case '\t' : g_string_append (html, MARK_AMP_STR "nbsp;" MARK_AMP_STR "nbsp;" MARK_AMP_STR "nbsp; ");
529                         break_dist=0; break; /* note the space at the end*/
530                 case ' ':
531                         break_dist = 0;
532                         if (space_seen) { /* second space in a row */
533                                 g_string_append (html, "&nbsp; ");
534                         } else
535                                 space_seen = TRUE;
536                         break;
537                 default:
538                         g_string_append_c (html, kar);
539                 }
540         }
541 }
542
543
544 static void
545 modest_text_utils_convert_buffer_to_html_finish (GString *html)
546 {
547         int i;
548         /* replace all our MARK_AMPs with real ones */
549         for (i = 0; i != html->len; ++i)
550                 if ((html->str)[i] == MARK_AMP || (html->str)[i] == MARK_AMP_URI)
551                         (html->str)[i] = '&';
552 }
553
554
555 gchar*
556 modest_text_utils_convert_to_html (const gchar *data)
557 {
558         GString         *html;      
559         gsize           len;
560
561         g_return_val_if_fail (data, NULL);
562         
563         if (!data)
564                 return NULL;
565
566         len = strlen (data);
567         html = g_string_sized_new (1.5 * len);  /* just a  guess... */
568
569         g_string_append_printf (html,
570                                 "<html><head>"
571                                 "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf8\">"
572                                 "</head>"
573                                 "<body>");
574
575         modest_text_utils_convert_buffer_to_html_start (html, data, -1);
576         
577         g_string_append (html, "</body></html>");
578
579         if (len <= HYPERLINKIFY_MAX_LENGTH)
580                 hyperlinkify_plain_text (html, 0);
581
582         modest_text_utils_convert_buffer_to_html_finish (html);
583         
584         return g_string_free (html, FALSE);
585 }
586
587 gchar *
588 modest_text_utils_convert_to_html_body (const gchar *data, gssize n, gboolean hyperlinkify)
589 {
590         GString         *html;      
591
592         g_return_val_if_fail (data, NULL);
593
594         if (!data)
595                 return NULL;
596
597         if (n == -1) 
598                 n = strlen (data);
599         html = g_string_sized_new (1.5 * n);    /* just a  guess... */
600
601         modest_text_utils_convert_buffer_to_html_start (html, data, n);
602
603         if (hyperlinkify && (n < HYPERLINKIFY_MAX_LENGTH))
604                 hyperlinkify_plain_text (html, 0);
605
606         modest_text_utils_convert_buffer_to_html_finish (html);
607         
608         return g_string_free (html, FALSE);
609 }
610
611 void
612 modest_text_utils_get_addresses_indexes (const gchar *addresses, GSList **start_indexes, GSList **end_indexes)
613 {
614         GString *str;
615         gchar *start, *cur;
616
617         if (!addresses)
618                 return;
619
620         if (strlen (addresses) == 0)
621                 return;
622
623         str = g_string_new ("");
624         start = (gchar*) addresses;
625         cur = (gchar*) addresses;
626
627         for (cur = start; *cur != '\0'; cur = g_utf8_next_char (cur)) {
628                 if (*cur == ',' || *cur == ';') {
629                         gint *start_index, *end_index;
630                         gchar *next_char = g_utf8_next_char (cur);
631
632                         if (!g_utf8_strchr (start, (cur - start + 1), g_utf8_get_char ("@")) &&
633                             next_char && *next_char != '\n' && *next_char != '\0')
634                                 continue;
635
636                         start_index = g_new0 (gint, 1);
637                         end_index = g_new0 (gint, 1);
638                         *start_index = g_utf8_pointer_to_offset (addresses, start);
639                         *end_index = g_utf8_pointer_to_offset (addresses, cur);;
640                         *start_indexes = g_slist_prepend (*start_indexes, start_index);
641                         *end_indexes = g_slist_prepend (*end_indexes, end_index);
642                         start = g_utf8_next_char (cur);
643                 }
644         }
645
646         if (start != cur) {
647                 gint *start_index, *end_index;
648                 start_index = g_new0 (gint, 1);
649                 end_index = g_new0 (gint, 1);
650                 *start_index = g_utf8_pointer_to_offset (addresses, start);
651                 *end_index = g_utf8_pointer_to_offset (addresses, cur);;
652                 *start_indexes = g_slist_prepend (*start_indexes, start_index);
653                 *end_indexes = g_slist_prepend (*end_indexes, end_index);
654         }
655
656         if (*start_indexes)
657                 *start_indexes = g_slist_reverse (*start_indexes);
658         if (*end_indexes)
659                 *end_indexes = g_slist_reverse (*end_indexes);
660 }
661
662
663 GSList *
664 modest_text_utils_split_addresses_list (const gchar *addresses)
665 {
666         GSList *head;
667         const gchar *my_addrs = addresses;
668         const gchar *end;
669         gchar *addr;
670         gboolean after_at = FALSE;
671
672         g_return_val_if_fail (addresses, NULL);
673
674         /* skip any space, ',', ';' '\n' at the start */
675         while (my_addrs && (my_addrs[0] == ' ' || my_addrs[0] == ',' ||
676                             my_addrs[0] == ';' || my_addrs[0] == '\n'))
677                ++my_addrs;
678
679         /* are we at the end of addresses list? */
680         if (!my_addrs[0])
681                 return NULL;
682
683         /* nope, we are at the start of some address
684          * now, let's find the end of the address */
685         end = my_addrs + 1;
686         while (end[0] && end[0] != ';' && !(after_at && end[0] == ',')) {
687                 if (end[0] == '\"') {
688                         while (end[0] && end[0] != '\"')
689                                 ++end;
690                 }
691                 if (end[0] == '@') {
692                         after_at = TRUE;
693                 }
694                 if ((end[0] && end[0] == '>')&&(end[1] && end[1] == ',')) {
695                         ++end;
696                         break;
697                 }
698                 ++end;
699         }
700
701         /* we got the address; copy it and remove trailing whitespace */
702         addr = g_strndup (my_addrs, end - my_addrs);
703         g_strchomp (addr);
704
705         remove_extra_spaces (addr);
706
707         head = g_slist_append (NULL, addr);
708         head->next = modest_text_utils_split_addresses_list (end); /* recurse */
709
710         return head;
711 }
712
713 gchar *
714 modest_text_utils_join_addresses (const gchar *from,
715                                   const gchar *to,
716                                   const gchar *cc,
717                                   const gchar *bcc)
718 {
719         GString *buffer;
720         gboolean add_separator = FALSE;
721
722         buffer = g_string_new ("");
723
724         if (from && strlen (from)) {
725                 buffer = g_string_append (buffer, from);
726                 add_separator = TRUE;
727         }
728         if (to && strlen (to)) {
729                 if (add_separator)
730                         buffer = g_string_append (buffer, "; ");
731                 else
732                         add_separator = TRUE;
733
734                 buffer = g_string_append (buffer, to);
735         }
736         if (cc && strlen (cc)) {
737                 if (add_separator)
738                         buffer = g_string_append (buffer, "; ");
739                 else
740                         add_separator = TRUE;
741
742                 buffer = g_string_append (buffer, cc);
743         }
744         if (bcc && strlen (bcc)) {
745                 if (add_separator)
746                         buffer = g_string_append (buffer, "; ");
747                 else
748                         add_separator = TRUE;
749
750                 buffer = g_string_append (buffer, bcc);
751         }
752
753         return g_string_free (buffer, FALSE);
754 }
755
756 void
757 modest_text_utils_address_range_at_position (const gchar *recipients_list,
758                                              guint position,
759                                              guint *start,
760                                              guint *end)
761 {
762         gchar *current = NULL;
763         gint range_start = 0;
764         gint range_end = 0;
765         gint index;
766         gboolean is_quoted = FALSE;
767
768         g_return_if_fail (recipients_list);
769         g_return_if_fail (position < g_utf8_strlen(recipients_list, -1));
770                 
771         index = 0;
772         for (current = (gchar *) recipients_list; *current != '\0';
773              current = g_utf8_find_next_char (current, NULL)) {
774                 gunichar c = g_utf8_get_char (current);
775
776                 if ((c == ',') && (!is_quoted)) {
777                         if (index < position) {
778                                 range_start = index + 1;
779                         } else {
780                                 break;
781                         }
782                 } else if (c == '\"') {
783                         is_quoted = !is_quoted;
784                 } else if ((c == ' ') &&(range_start == index)) {
785                         range_start ++;
786                 }
787                 index ++;
788                 range_end = index;
789         }
790
791         if (start)
792                 *start = range_start;
793         if (end)
794                 *end = range_end;
795 }
796
797 gchar *
798 modest_text_utils_address_with_standard_length (const gchar *recipients_list)
799 {
800         gchar ** splitted;
801         gchar ** current;
802         GString *buffer = g_string_new ("");
803
804         splitted = g_strsplit (recipients_list, "\n", 0);
805         current = splitted;
806         while (*current) {
807                 gchar *line;
808                 if (current != splitted)
809                         buffer = g_string_append_c (buffer, '\n');
810                 line = g_strndup (*splitted, 1000);
811                 buffer = g_string_append (buffer, line);
812                 g_free (line);
813                 current++;
814         }
815
816         g_strfreev (splitted);
817
818         return g_string_free (buffer, FALSE);
819 }
820
821
822 /* ******************************************************************* */
823 /* ************************* UTILIY FUNCTIONS ************************ */
824 /* ******************************************************************* */
825
826 static GString *
827 get_next_line (const gchar * b, const gsize blen, const gchar * iter)
828 {
829         GString *gs;
830         const gchar *i0;
831         
832         if (iter > b + blen)
833                 return g_string_new("");
834         
835         i0 = iter;
836         while (iter[0]) {
837                 if (iter[0] == '\n')
838                         break;
839                 iter++;
840         }
841         gs = g_string_new_len (i0, iter - i0);
842         return gs;
843 }
844 static int
845 get_indent_level (const char *l)
846 {
847         int indent = 0;
848
849         while (l[0]) {
850                 if (l[0] == '>') {
851                         indent++;
852                         if (l[1] == ' ') {
853                                 l++;
854                         }
855                 } else {
856                         break;
857                 }
858                 l++;
859
860         }
861
862         /*      if we hit the signature marker "-- ", we return -(indent + 1). This
863          *      stops reformatting.
864          */
865         if (strcmp (l, MODEST_TEXT_UTILS_SIGNATURE_MARKER) == 0) {
866                 return -1 - indent;
867         } else {
868                 return indent;
869         }
870 }
871
872 static void
873 unquote_line (GString * l, const gchar *quote_symbol)
874 {
875         gchar *p;
876         gint quote_len;
877
878         p = l->str;
879         quote_len = strlen (quote_symbol);
880         while (p[0]) {
881                 if (g_str_has_prefix (p, quote_symbol)) {
882                         if (p[quote_len] == ' ') {
883                                 p += quote_len;
884                         }
885                 } else {
886                         break;
887                 }
888                 p++;
889         }
890         g_string_erase (l, 0, p - l->str);
891 }
892
893 static void
894 append_quoted (GString * buf, const gchar *quote_symbol,
895                int indent, const GString * str,
896                const int cutpoint)
897 {
898         int i;
899         gchar *quote_concat;
900
901         indent = indent < 0 ? abs (indent) - 1 : indent;
902         quote_concat = g_strconcat (quote_symbol, " ", NULL);
903         for (i = 0; i <= indent; i++) {
904                 g_string_append (buf, quote_concat);
905         }
906         g_free (quote_concat);
907         if (cutpoint > 0) {
908                 g_string_append_len (buf, str->str, cutpoint);
909         } else {
910                 g_string_append (buf, str->str);
911         }
912         g_string_append (buf, "\n");
913 }
914
915 static int
916 get_breakpoint_utf8 (const gchar * s, gint indent, const gint limit)
917 {
918         gint index = 0;
919         const gchar *pos, *last;
920         gunichar *uni;
921
922         if (2*indent >= limit)
923                 return strlen (s);
924
925         indent = indent < 0 ? abs (indent) - 1 : indent;
926
927         last = NULL;
928         pos = s;
929         uni = g_utf8_to_ucs4_fast (s, -1, NULL);
930         while (pos[0]) {
931                 if ((index + 2 * indent > limit) && last) {
932                         g_free (uni);
933                         return last - s;
934                 }
935                 if (g_unichar_isspace (uni[index])) {
936                         last = pos;
937                 }
938                 pos = g_utf8_next_char (pos);
939                 index++;
940         }
941         g_free (uni);
942         return strlen (s);
943 }
944
945 static int
946 get_breakpoint_ascii (const gchar * s, const gint indent, const gint limit)
947 {
948         gint i, last;
949
950         last = strlen (s);
951         if (last + 2 * indent < limit)
952                 return last;
953
954         for (i = strlen (s); i > 0; i--) {
955                 if (s[i] == ' ') {
956                         if (i + 2 * indent <= limit) {
957                                 return i;
958                         } else {
959                                 last = i;
960                         }
961                 }
962         }
963         return last;
964 }
965
966 static int
967 get_breakpoint (const gchar * s, const gint indent, const gint limit)
968 {
969
970         if (g_utf8_validate (s, -1, NULL)) {
971                 return get_breakpoint_utf8 (s, indent, limit);
972         } else {                /* assume ASCII */
973                 //g_warning("invalid UTF-8 in msg");
974                 return get_breakpoint_ascii (s, indent, limit);
975         }
976 }
977
978 static gchar *
979 cite (const time_t sent_date, const gchar *from)
980 {
981         return g_strdup (_("mcen_ia_editor_original_message"));
982 }
983
984 static gchar *
985 quoted_attachments (GList *attachments)
986 {
987         GList *node = NULL;
988         GString *result = g_string_new ("");
989         for (node = attachments; node != NULL; node = g_list_next (node)) {
990                 gchar *filename = (gchar *) node->data;
991                 g_string_append_printf ( result, "%s %s\n", _("mcen_ia_editor_attach_filename"), filename);
992         }
993
994         return g_string_free (result, FALSE);
995
996 }
997
998 static GString *
999 modest_text_utils_quote_body (GString *output, const gchar *text,
1000                               const gchar *quote_symbol,
1001                               int limit)
1002 {
1003
1004         const gchar *iter;
1005         gsize len;
1006         gint indent = 0, breakpoint, rem_indent = 0;
1007         GString *l, *remaining;
1008         gchar *forced_wrap_append;
1009
1010         iter = text;
1011         len = strlen(text);
1012         remaining = g_string_new ("");
1013         forced_wrap_append = NULL;
1014         do {
1015
1016                 if (forced_wrap_append) {
1017                         gint next_line_indent;
1018                         gint l_len_with_indent;
1019
1020                         g_string_erase (remaining, 0, -1);
1021                         next_line_indent = get_indent_level (iter);
1022                         l = get_next_line (text, len, iter);
1023                         l_len_with_indent = l->len;
1024                         unquote_line (l, quote_symbol);
1025                         if ((l->str && l->str[0] == '\0') || (next_line_indent != indent)) {
1026                                 g_string_free (l, TRUE);
1027                                 l = g_string_new (forced_wrap_append);
1028                         } else {
1029                                 gunichar first_in_l;
1030                                 iter = iter + l_len_with_indent + 1;
1031                                 first_in_l = g_utf8_get_char_validated (l->str, l->len);
1032                                 if (!g_unichar_isspace (first_in_l)) 
1033                                         l = g_string_prepend (l, " ");
1034                                 l = g_string_prepend (l, forced_wrap_append);
1035                         }
1036                         g_free (forced_wrap_append);
1037                         forced_wrap_append = NULL;
1038                 } else {
1039                         l = get_next_line (text, len, iter);
1040                         iter = iter + l->len + 1;
1041                         indent = get_indent_level (l->str);
1042                         unquote_line (l, quote_symbol);
1043                 }
1044
1045                 if (remaining->len) {
1046                         if (l->len && indent == rem_indent) {
1047                                 g_string_prepend (l, " ");
1048                                 g_string_prepend (l, remaining->str);
1049                         } else {
1050                                 do {
1051                                         gunichar remaining_first;
1052                                         breakpoint =
1053                                                 get_breakpoint (remaining->str,
1054                                                                 rem_indent,
1055                                                                 limit);
1056                                         if (breakpoint < remaining->len) {
1057                                                 g_free (forced_wrap_append);
1058                                                 forced_wrap_append = g_strdup (remaining->str + breakpoint);
1059                                         } else {
1060                                                 if (!forced_wrap_append)
1061                                                         append_quoted (output, quote_symbol, rem_indent,
1062                                                                        remaining, breakpoint);
1063                                         }
1064                                         g_string_erase (remaining, 0,
1065                                                         breakpoint);
1066                                         remaining_first = g_utf8_get_char_validated (remaining->str, remaining->len);
1067                                         if (remaining_first != ((gunichar) -1)) {
1068                                                 if (g_unichar_isspace (remaining_first)) {
1069                                                         g_string_erase (remaining, 0, g_utf8_next_char (remaining->str) - remaining->str);
1070                                                 }
1071                                         }
1072                                 } while (remaining->len);
1073                         }
1074                 }
1075                 g_string_free (remaining, TRUE);
1076                 breakpoint = get_breakpoint (l->str, indent, limit);
1077                 remaining = g_string_new (l->str + breakpoint);
1078                 if (remaining->str[0] == ' ') {
1079                         g_string_erase (remaining, 0, 1);
1080                 }
1081                 rem_indent = indent;
1082                 if (remaining->len > 0) {
1083                         g_free (forced_wrap_append);
1084                         forced_wrap_append = g_strdup (remaining->str);
1085                 }
1086                 append_quoted (output, quote_symbol, indent, l, breakpoint);
1087                 g_string_free (l, TRUE);
1088         } while ((iter < text + len) || (remaining->str[0]) || forced_wrap_append);
1089
1090         return output;
1091 }
1092
1093 static gchar *
1094 modest_text_utils_quote_plain_text (const gchar *text, 
1095                                     const gchar *cite, 
1096                                     const gchar *signature,
1097                                     GList *attachments,
1098                                     int limit)
1099 {
1100         GString *q;
1101         gchar *attachments_string = NULL;
1102
1103         q = g_string_new ("");
1104
1105         if (signature != NULL) {
1106                 g_string_append_printf (q, "\n%s\n", MODEST_TEXT_UTILS_SIGNATURE_MARKER);
1107                 q = g_string_append (q, signature);
1108         }
1109
1110         q = g_string_append (q, "\n");
1111         q = g_string_append (q, cite);
1112         q = g_string_append_c (q, '\n');
1113
1114         q = modest_text_utils_quote_body (q, text, ">", limit);
1115
1116         attachments_string = quoted_attachments (attachments);
1117         q = g_string_append (q, attachments_string);
1118         g_free (attachments_string);
1119
1120         return g_string_free (q, FALSE);
1121 }
1122
1123 static void
1124 quote_html_add_to_gstring (GString *string,
1125                            const gchar *text)
1126 {
1127         if (text && strcmp (text, "")) {
1128                 gchar *html_text = modest_text_utils_convert_to_html_body (text, -1, TRUE);
1129                 g_string_append_printf (string, "%s<br/>", html_text);
1130                 g_free (html_text);
1131         }
1132 }
1133
1134 static gchar*
1135 modest_text_utils_quote_html (const gchar *text, 
1136                               const gchar *cite, 
1137                               const gchar *signature,
1138                               GList *attachments,
1139                               int limit)
1140 {
1141         GString *result_string;
1142
1143         result_string = 
1144                 g_string_new ( \
1145                               "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" \
1146                               "<html>\n"                                \
1147                               "<body>\n<br/>\n");
1148
1149         if (text || cite || signature) {
1150                 GString *quoted_text;
1151                 g_string_append (result_string, "<pre>\n");
1152                 if (signature) {
1153                         quote_html_add_to_gstring (result_string, MODEST_TEXT_UTILS_SIGNATURE_MARKER);
1154                         quote_html_add_to_gstring (result_string, signature);
1155                 }
1156                 quote_html_add_to_gstring (result_string, cite);
1157                 quoted_text = g_string_new ("");
1158                 quoted_text = modest_text_utils_quote_body (quoted_text, (text) ? text : "", ">", limit);
1159                 quote_html_add_to_gstring (result_string, quoted_text->str);
1160                 g_string_free (quoted_text, TRUE);
1161                 if (attachments) {
1162                         gchar *attachments_string = quoted_attachments (attachments);
1163                         quote_html_add_to_gstring (result_string, attachments_string);
1164                         g_free (attachments_string);
1165                 }
1166                 g_string_append (result_string, "</pre>");
1167         }
1168         g_string_append (result_string, "</body>");
1169         g_string_append (result_string, "</html>");
1170
1171         return g_string_free (result_string, FALSE);
1172 }
1173
1174 static gint 
1175 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
1176 {
1177         return match2->offset - match1->offset;
1178 }
1179
1180 static gint url_matches_block = 0;
1181 static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
1182 static GMutex *url_patterns_mutex = NULL;
1183
1184
1185 static gboolean
1186 compile_patterns ()
1187 {
1188         guint i;
1189         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
1190         for (i = 0; i != pattern_num; ++i) {
1191                 patterns[i].preg = g_slice_new0 (regex_t);
1192                 
1193                 /* this should not happen */
1194                 if (regcomp (patterns[i].preg, patterns[i].regex,
1195                              REG_ICASE|REG_EXTENDED|REG_NEWLINE) != 0) {
1196                         g_warning ("%s: error in regexp:\n%s\n", __FUNCTION__, patterns[i].regex);
1197                         return FALSE;
1198                 }
1199         }
1200         return TRUE;
1201 }
1202
1203 static void 
1204 free_patterns ()
1205 {
1206         guint i;
1207         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
1208         for (i = 0; i != pattern_num; ++i) {
1209                 regfree (patterns[i].preg);
1210                 g_slice_free  (regex_t, patterns[i].preg);
1211         } /* don't free patterns itself -- it's static */
1212 }
1213
1214 void
1215 modest_text_utils_hyperlinkify_begin (void)
1216 {
1217
1218         if (url_patterns_mutex == NULL) {
1219                 url_patterns_mutex = g_mutex_new ();
1220         }
1221         g_mutex_lock (url_patterns_mutex);
1222         if (url_matches_block == 0)
1223                 compile_patterns ();
1224         url_matches_block ++;
1225         g_mutex_unlock (url_patterns_mutex);
1226 }
1227
1228 void
1229 modest_text_utils_hyperlinkify_end (void)
1230 {
1231         g_mutex_lock (url_patterns_mutex);
1232         url_matches_block--;
1233         if (url_matches_block <= 0)
1234                 free_patterns ();
1235         g_mutex_unlock (url_patterns_mutex);
1236 }
1237
1238
1239 static GSList*
1240 get_url_matches (GString *txt, gint offset)
1241 {
1242         regmatch_t rm;
1243         guint rv, i, tmp_offset = 0;
1244         GSList *match_list = NULL;
1245
1246         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
1247
1248         /* initalize the regexps */
1249         modest_text_utils_hyperlinkify_begin ();
1250
1251         /* find all the matches */
1252         for (i = 0; i != pattern_num; ++i) {
1253                 tmp_offset     = offset;        
1254                 while (1) {
1255                         url_match_t *match;
1256                         gboolean is_submatch;
1257                         GSList *cursor;
1258                         
1259                         if ((rv = regexec (patterns[i].preg, txt->str + tmp_offset, 1, &rm, 0)) != 0) {
1260                                 g_return_val_if_fail (rv == REG_NOMATCH, NULL); /* this should not happen */
1261                                 break; /* try next regexp */ 
1262                         }
1263                         if (rm.rm_so == -1)
1264                                 break;
1265                         
1266                         is_submatch = FALSE;
1267                         /* check  old matches to see if this has already been matched */
1268                         cursor = match_list;
1269                         while (cursor && !is_submatch) {
1270                                 const url_match_t *old_match =
1271                                         (const url_match_t *) cursor->data;
1272                                 guint new_offset = tmp_offset + rm.rm_so;
1273                                 is_submatch = (new_offset >  old_match->offset &&
1274                                                new_offset <  old_match->offset + old_match->len);
1275                                 cursor = g_slist_next (cursor);
1276                         }
1277
1278                         if (!is_submatch) {
1279                                 /* make a list of our matches (<offset, len, prefix> tupels)*/
1280                                 match = g_slice_new (url_match_t);
1281                                 match->offset = tmp_offset + rm.rm_so;
1282                                 match->len    = rm.rm_eo - rm.rm_so;
1283                                 match->prefix = patterns[i].prefix;
1284                                 match_list = g_slist_prepend (match_list, match);
1285                         }               
1286                         tmp_offset += rm.rm_eo;
1287                 }
1288         }
1289
1290         modest_text_utils_hyperlinkify_end ();
1291         
1292         /* now sort the list, so the matches are in reverse order of occurence.
1293          * that way, we can do the replacements starting from the end, so we don't need
1294          * to recalculate the offsets
1295          */
1296         match_list = g_slist_sort (match_list,
1297                                    (GCompareFunc)cmp_offsets_reverse); 
1298         return match_list;      
1299 }
1300
1301
1302
1303 /* replace all occurences of needle in haystack with repl*/
1304 static gchar*
1305 replace_string (const gchar *haystack, const gchar *needle, gchar repl)
1306 {
1307         gchar *str, *cursor;
1308
1309         if (!haystack || !needle || strlen(needle) == 0)
1310                 return haystack ? g_strdup(haystack) : NULL;
1311         
1312         str = g_strdup (haystack);
1313
1314         for (cursor = str; cursor && *cursor; ++cursor) {
1315                 if (g_str_has_prefix (cursor, needle)) {
1316                         cursor[0] = repl;
1317                         memmove (cursor + 1,
1318                                  cursor + strlen (needle),
1319                                  strlen (cursor + strlen (needle)) + 1);
1320                 }
1321         }
1322         
1323         return str;
1324 }
1325
1326 static void
1327 hyperlinkify_plain_text (GString *txt, gint offset)
1328 {
1329         GSList *cursor;
1330         GSList *match_list = get_url_matches (txt, offset);
1331
1332         /* we will work backwards, so the offsets stay valid */
1333         for (cursor = match_list; cursor; cursor = cursor->next) {
1334
1335                 url_match_t *match = (url_match_t*) cursor->data;
1336                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
1337                 gchar *repl = NULL; /* replacement  */
1338
1339                 /* the string still contains $(MARK_AMP_URI_STR)"amp;" for each
1340                  * '&' in the original, because of the text->html conversion.
1341                  * in the href-URL (and only there), we must convert that back to
1342                  * '&'
1343                  */
1344                 gchar *href_url = replace_string (url, MARK_AMP_URI_STR "amp;", '&');
1345                 
1346                 /* the prefix is NULL: use the one that is already there */
1347                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
1348                                         match->prefix ? match->prefix : EMPTY_STRING, 
1349                                         href_url, url);
1350
1351                 /* replace the old thing with our hyperlink
1352                  * replacement thing */
1353                 g_string_erase  (txt, match->offset, match->len);
1354                 g_string_insert (txt, match->offset, repl);
1355                 
1356                 g_free (url);
1357                 g_free (repl);
1358                 g_free (href_url);
1359
1360                 g_slice_free (url_match_t, match);      
1361         }
1362         
1363         g_slist_free (match_list);
1364 }
1365
1366 void
1367 modest_text_utils_hyperlinkify (GString *string_buffer)
1368 {
1369         gchar *after_body;
1370         gint offset = 0;
1371
1372         after_body = strstr (string_buffer->str, "<body>");
1373         if (after_body != NULL)
1374                 offset = after_body - string_buffer->str;
1375         hyperlinkify_plain_text (string_buffer, offset);
1376 }
1377
1378
1379 /* for optimization reasons, we change the string in-place */
1380 void
1381 modest_text_utils_get_display_address (gchar *address)
1382 {
1383         int i;
1384
1385         g_return_if_fail (address);
1386         
1387         if (!address)
1388                 return;
1389         
1390         /* should not be needed, and otherwise, we probably won't screw up the address
1391          * more than it already is :) 
1392          * g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL);
1393          * */
1394         
1395         /* remove leading whitespace */
1396         if (address[0] == ' ')
1397                 g_strchug (address);
1398                 
1399         for (i = 0; address[i]; ++i) {
1400                 if (address[i] == '<') {
1401                         if (G_UNLIKELY(i == 0)) {
1402                                 break; /* there's nothing else, leave it */
1403                         }else {
1404                                 address[i] = '\0'; /* terminate the string here */
1405                                 break;
1406                         }
1407                 }
1408         }
1409
1410         g_strchomp (address);
1411 }
1412
1413
1414 gchar *
1415 modest_text_utils_get_display_addresses (const gchar *recipients)
1416 {
1417         gchar *addresses;
1418         GSList *recipient_list;
1419
1420         addresses = NULL;
1421         recipient_list = modest_text_utils_split_addresses_list (recipients);
1422         if (recipient_list) {
1423                 GString *add_string = g_string_sized_new (strlen (recipients));
1424                 GSList *iter = recipient_list;
1425                 gboolean first = TRUE;
1426
1427                 while (iter) {
1428                         /* Strings are changed in place */
1429                         modest_text_utils_get_display_address ((gchar *) iter->data);
1430                         if (G_UNLIKELY (first)) {
1431                                 g_string_append_printf (add_string, "%s", (gchar *) iter->data);
1432                                 first = FALSE;
1433                         } else {
1434                                 g_string_append_printf (add_string, ", %s", (gchar *) iter->data);
1435                         }
1436                         iter = g_slist_next (iter);
1437                 }
1438                 g_slist_foreach (recipient_list, (GFunc) g_free, NULL);
1439                 g_slist_free (recipient_list);
1440                 addresses = g_string_free (add_string, FALSE);
1441         }
1442
1443         return addresses;
1444 }
1445
1446
1447 gchar *
1448 modest_text_utils_get_email_address (const gchar *full_address)
1449 {
1450         const gchar *left, *right;
1451
1452         g_return_val_if_fail (full_address, NULL);
1453         
1454         if (!full_address)
1455                 return NULL;
1456         
1457         g_return_val_if_fail (g_utf8_validate (full_address, -1, NULL), NULL);
1458         
1459         left = g_strrstr_len (full_address, strlen(full_address), "<");
1460         if (left == NULL)
1461                 return g_strdup (full_address);
1462
1463         right = g_strstr_len (left, strlen(left), ">");
1464         if (right == NULL)
1465                 return g_strdup (full_address);
1466
1467         return g_strndup (left + 1, right - left - 1);
1468 }
1469
1470 gint 
1471 modest_text_utils_get_subject_prefix_len (const gchar *sub)
1472 {
1473         gint prefix_len = 0;    
1474
1475         g_return_val_if_fail (sub, 0);
1476
1477         if (!sub)
1478                 return 0;
1479         
1480         /* optimization: "Re", "RE", "re","Fwd", "FWD", "fwd","FW","Fw", "fw" */
1481         if (sub[0] != 'R' && sub[0] != 'F' && sub[0] != 'r' && sub[0] != 'f')
1482                 return 0;
1483         else if (sub[0] && sub[1] != 'e' && sub[1] != 'E' && sub[1] != 'w' && sub[1] != 'W')
1484                 return 0;
1485
1486         prefix_len = 2;
1487         if (sub[2] == 'd')
1488                 ++prefix_len;
1489
1490         /* skip over a [...] block */
1491         if (sub[prefix_len] == '[') {
1492                 int c = prefix_len + 1;
1493                 while (sub[c] && sub[c] != ']')
1494                         ++c;
1495                 if (!sub[c])
1496                         return 0; /* no end to the ']' found */
1497                 else
1498                         prefix_len = c + 1;
1499         }
1500
1501         /* did we find the ':' ? */
1502         if (sub[prefix_len] == ':') {
1503                 ++prefix_len;
1504                 if (sub[prefix_len] == ' ')
1505                         ++prefix_len;
1506                 prefix_len += modest_text_utils_get_subject_prefix_len (sub + prefix_len);
1507 /*              g_warning ("['%s','%s']", sub, (char*) sub + prefix_len); */
1508                 return prefix_len;
1509         } else
1510                 return 0;
1511 }
1512
1513
1514 gint
1515 modest_text_utils_utf8_strcmp (const gchar* s1, const gchar *s2, gboolean insensitive)
1516 {
1517
1518 /* work even when s1 and/or s2 == NULL */
1519         if (G_UNLIKELY(s1 == s2))
1520                 return 0;
1521         if (G_UNLIKELY(!s1))
1522                 return -1;
1523         if (G_UNLIKELY(!s2))
1524                 return 1;
1525         
1526         /* if it's not case sensitive */
1527         if (!insensitive) {
1528
1529                 /* optimization: shortcut if first char is ascii */ 
1530                 if (((s1[0] & 0x80)== 0) && ((s2[0] & 0x80) == 0) &&
1531                     (s1[0] != s2[0])) 
1532                         return s1[0] - s2[0];
1533                 
1534                 return g_utf8_collate (s1, s2);
1535
1536         } else {
1537                 gint result;
1538                 gchar *n1, *n2;
1539
1540                 /* optimization: shortcut if first char is ascii */ 
1541                 if (((s1[0] & 0x80) == 0) && ((s2[0] & 0x80) == 0) &&
1542                     (tolower(s1[0]) != tolower (s2[0]))) 
1543                         return tolower(s1[0]) - tolower(s2[0]);
1544                 
1545                 n1 = g_utf8_strdown (s1, -1);
1546                 n2 = g_utf8_strdown (s2, -1);
1547                 
1548                 result = g_utf8_collate (n1, n2);
1549                 
1550                 g_free (n1);
1551                 g_free (n2);
1552         
1553                 return result;
1554         }
1555 }
1556
1557
1558 const gchar*
1559 modest_text_utils_get_display_date (time_t date)
1560 {
1561 #define DATE_BUF_SIZE 64 
1562         static gchar date_buf[DATE_BUF_SIZE];
1563         
1564         /* calculate the # of days since epoch for 
1565          * for today and for the date provided 
1566          * based on idea from pvanhoof */
1567         int day      = time(NULL) / (24 * 60 * 60);
1568         int date_day = date       / (24 * 60 * 60);
1569
1570         /* if it's today, show the time, if it's not today, show the date instead */
1571
1572         /* TODO: take into account the system config for 24/12h */
1573 #ifdef MODEST_TOOLKIT_HILDON2
1574         if (day == date_day) /* is the date today? */
1575                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, _HL("wdgt_va_24h_time"), date);
1576         else 
1577                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, _HL("wdgt_va_date"), date); 
1578 #else
1579         if (day == date_day) /* is the date today? */
1580                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%X", date);
1581         else 
1582                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%x", date); 
1583 #endif
1584
1585         return date_buf; /* this is a static buffer, don't free! */
1586 }
1587
1588
1589
1590 gboolean
1591 modest_text_utils_validate_folder_name (const gchar *folder_name)
1592 {
1593         /* based on http://msdn2.microsoft.com/en-us/library/aa365247.aspx,
1594          * with some extras */
1595         
1596         guint len;
1597         gint i;
1598         const gchar **cursor = NULL;
1599         const gchar *forbidden_names[] = { /* windows does not like these */
1600                 "CON", "PRN", "AUX", "NUL", ".", "..", "cur", "tmp", "new", 
1601                 NULL /* cur, tmp, new are reserved for Maildir */
1602         };
1603         
1604         /* cannot be NULL */
1605         if (!folder_name) 
1606                 return FALSE;
1607
1608         /* cannot be empty */
1609         len = strlen(folder_name);
1610         if (len == 0)
1611                 return FALSE;
1612         
1613         /* cannot start with a dot, vfat does not seem to like that */
1614         if (folder_name[0] == '.')
1615                 return FALSE;
1616
1617         /* cannot start or end with a space */
1618         if (g_ascii_isspace(folder_name[0]) || g_ascii_isspace(folder_name[len - 1]))
1619                 return FALSE; 
1620
1621         /* cannot contain a forbidden char */   
1622         for (i = 0; i < len; i++)
1623                 if (modest_text_utils_is_forbidden_char (folder_name[i], FOLDER_NAME_FORBIDDEN_CHARS))
1624                         return FALSE;
1625
1626         /* Cannot contain Windows port numbers. I'd like to use GRegex
1627            but it's still not available in Maemo. sergio */
1628         if (!g_ascii_strncasecmp (folder_name, "LPT", 3) ||
1629             !g_ascii_strncasecmp (folder_name, "COM", 3)) {
1630                 glong val;
1631                 gchar *endptr;
1632
1633                 /* We skip the first 3 characters for the
1634                    comparison */
1635                 val = strtol(folder_name+3, &endptr, 10);
1636
1637                 /* If the conversion to long succeeded then the string
1638                    is not valid for us */
1639                 if (*endptr == '\0')
1640                         return FALSE;
1641                 else
1642                         return TRUE;
1643         }
1644         
1645         /* cannot contain a forbidden word */
1646         if (len <= 4) {
1647                 for (cursor = forbidden_names; cursor && *cursor; ++cursor) {
1648                         if (g_ascii_strcasecmp (folder_name, *cursor) == 0)
1649                                 return FALSE;
1650                 }
1651         }
1652
1653         return TRUE; /* it's valid! */
1654 }
1655
1656
1657
1658 gboolean
1659 modest_text_utils_validate_domain_name (const gchar *domain)
1660 {
1661         gboolean valid = FALSE;
1662         regex_t rx;
1663         const gchar* domain_regex = "^([a-z0-9-]*[a-z0-9]\\.)+[a-z0-9-]*[a-z0-9]$";
1664
1665         g_return_val_if_fail (domain, FALSE);
1666         
1667         if (!domain)
1668                 return FALSE;
1669         
1670         memset (&rx, 0, sizeof(regex_t)); /* coverity wants this... */
1671                 
1672         /* domain name: all alphanum or '-' or '.',
1673          * but beginning/ending in alphanum */  
1674         if (regcomp (&rx, domain_regex, REG_ICASE|REG_EXTENDED|REG_NOSUB)) {
1675                 g_warning ("BUG: error in regexp");
1676                 return FALSE;
1677         }
1678         
1679         valid = (regexec (&rx, domain, 1, NULL, 0) == 0);
1680         regfree (&rx);
1681                 
1682         return valid;
1683 }
1684
1685
1686
1687 gboolean
1688 modest_text_utils_validate_email_address (const gchar *email_address,
1689                                           const gchar **invalid_char_position)
1690 {
1691         int count = 0;
1692         const gchar *c = NULL, *domain = NULL;
1693         static gchar *rfc822_specials = "()<>@,;:\\\"[]&";
1694         
1695         if (invalid_char_position)
1696                 *invalid_char_position = NULL;
1697         
1698         g_return_val_if_fail (email_address, FALSE);
1699         
1700         /* check that the email adress contains exactly one @ */
1701         if (!strstr(email_address, "@") || 
1702                         (strstr(email_address, "@") != g_strrstr(email_address, "@"))) 
1703                 return FALSE;
1704         
1705         /* first we validate the name portion (name@domain) */
1706         for (c = email_address;  *c;  c++) {
1707                 if (*c == '\"' && 
1708                     (c == email_address || 
1709                      *(c - 1) == '.' || 
1710                      *(c - 1) == '\"')) {
1711                         while (*++c) {
1712                                 if (*c == '\"') 
1713                                         break;
1714                                 if (*c == '\\' && (*++c == ' ')) 
1715                                         continue;
1716                                 if (*c <= ' ' || *c >= 127) 
1717                                         return FALSE;
1718                         }
1719                         if (!*c++) 
1720                                 return FALSE;
1721                         if (*c == '@') 
1722                                 break;
1723                         if (*c != '.') 
1724                                 return FALSE;
1725                         continue;
1726                 }
1727                 if (*c == '@') 
1728                         break;
1729                 if (*c <= ' ' || *c >= 127) 
1730                         return FALSE;
1731                 if (strchr(rfc822_specials, *c)) {
1732                         if (invalid_char_position)
1733                                 *invalid_char_position = c;
1734                         return FALSE;
1735                 }
1736         }
1737         if (c == email_address || *(c - 1) == '.') 
1738                 return FALSE;
1739
1740         /* next we validate the domain portion (name@domain) */
1741         if (!*(domain = ++c)) 
1742                 return FALSE;
1743         do {
1744                 if (*c == '.') {
1745                         if (c == domain || *(c - 1) == '.' || *(c + 1) == '\0') 
1746                                 return FALSE;
1747                         count++;
1748                 }
1749                 if (*c <= ' ' || *c >= 127) 
1750                         return FALSE;
1751                 if (strchr(rfc822_specials, *c)) {
1752                         if (invalid_char_position)
1753                                 *invalid_char_position = c;
1754                         return FALSE;
1755                 }
1756         } while (*++c);
1757
1758         return (count >= 1) ? TRUE : FALSE;
1759 }
1760
1761 gboolean 
1762 modest_text_utils_validate_recipient (const gchar *recipient, const gchar **invalid_char_position)
1763 {
1764         gchar *stripped, *current;
1765         gchar *right_part;
1766         gboolean has_error = FALSE;
1767
1768         if (invalid_char_position)
1769                 *invalid_char_position = NULL;
1770         
1771         g_return_val_if_fail (recipient, FALSE);
1772         
1773         if (modest_text_utils_validate_email_address (recipient, invalid_char_position))
1774                 return TRUE;
1775
1776         stripped = g_strdup (recipient);
1777         stripped = g_strstrip (stripped);
1778         current = stripped;
1779
1780         if (*current == '\0') {
1781                 g_free (stripped);
1782                 return FALSE;
1783         }
1784
1785         /* quoted string */
1786         if (*current == '\"') {
1787                 gchar *last_quote = NULL;
1788                 current = g_utf8_next_char (current);
1789                 has_error = TRUE;
1790                 for (; *current != '\0'; current = g_utf8_next_char (current)) {
1791                         if (*current == '\\') {
1792                                 /* TODO: This causes a warning, which breaks the build, 
1793                                  * because a gchar cannot be < 0.
1794                                  * murrayc. 
1795                                 if (current[1] <0) {
1796                                         has_error = TRUE;
1797                                         break;
1798                                 }
1799                                 */
1800                         } else if (*current == '\"') {
1801                                 has_error = FALSE;
1802                                 current = g_utf8_next_char (current);
1803                                 last_quote = current;
1804                         }
1805                 }
1806                 if (last_quote)
1807                         current = g_utf8_next_char (last_quote);
1808         } else {
1809                 has_error = TRUE;
1810                 for (current = stripped ; *current != '\0'; current = g_utf8_next_char (current)) {
1811                         if (*current == '<') {
1812                                 has_error = FALSE;
1813                                 break;
1814                         }
1815                 }
1816         }
1817                 
1818         if (has_error) {
1819                 g_free (stripped);
1820                 return FALSE;
1821         }
1822
1823         right_part = g_strdup (current);
1824         g_free (stripped);
1825         right_part = g_strstrip (right_part);
1826
1827         if (g_str_has_suffix (right_part, ",") || g_str_has_suffix (right_part, ";"))
1828                right_part [(strlen (right_part) - 1)] = '\0';
1829
1830         if (g_str_has_prefix (right_part, "<") &&
1831             g_str_has_suffix (right_part, ">")) {
1832                 gchar *address;
1833                 gboolean valid;
1834
1835                 address = g_strndup (right_part+1, strlen (right_part) - 2);
1836                 g_free (right_part);
1837                 valid = modest_text_utils_validate_email_address (address, invalid_char_position);
1838                 g_free (address);
1839                 return valid;
1840         } else {
1841                 g_free (right_part);
1842                 return FALSE;
1843         }
1844 }
1845
1846
1847 gchar *
1848 modest_text_utils_get_display_size (guint64 size)
1849 {
1850         const guint KB=1024;
1851         const guint MB=1024 * KB;
1852         const guint GB=1024 * MB;
1853
1854         if (size == 0)
1855                 return g_strdup_printf (_FM("sfil_li_size_kb"), (int) 0);
1856         if (0 <= size && size < KB)
1857                 return g_strdup_printf (_FM("sfil_li_size_1kb_99kb"), (int) 1);
1858         else if (KB <= size && size < 100 * KB)
1859                 return g_strdup_printf (_FM("sfil_li_size_1kb_99kb"), (int) size / KB);
1860         else if (100*KB <= size && size < MB)
1861                 return g_strdup_printf (_FM("sfil_li_size_100kb_1mb"), (int) size / KB);
1862         else if (MB <= size && size < 10*MB)
1863                 return g_strdup_printf (_FM("sfil_li_size_1mb_10mb"), (float) size / MB);
1864         else if (10*MB <= size && size < GB)
1865                 return g_strdup_printf (_FM("sfil_li_size_10mb_1gb"), (float) size / MB);
1866         else
1867                 return g_strdup_printf (_FM("sfil_li_size_1gb_or_greater"), (float) size / GB);
1868 }
1869
1870 static gchar *
1871 get_email_from_address (const gchar * address)
1872 {
1873         gchar *left_limit, *right_limit;
1874
1875         left_limit = strstr (address, "<");
1876         right_limit = g_strrstr (address, ">");
1877
1878         if ((left_limit == NULL)||(right_limit == NULL)|| (left_limit > right_limit))
1879                 return g_strdup (address);
1880         else
1881                 return g_strndup (left_limit + 1, (right_limit - left_limit) - 1);
1882 }
1883
1884 gchar *
1885 modest_text_utils_get_color_string (GdkColor *color)
1886 {
1887         g_return_val_if_fail (color, NULL);
1888
1889         return g_strdup_printf ("#%x%x%x%x%x%x%x%x%x%x%x%x",
1890                                 (color->red >> 12)   & 0xf, (color->red >> 8)   & 0xf,
1891                                 (color->red >>  4)   & 0xf, (color->red)        & 0xf,
1892                                 (color->green >> 12) & 0xf, (color->green >> 8) & 0xf,
1893                                 (color->green >>  4) & 0xf, (color->green)      & 0xf,
1894                                 (color->blue >> 12)  & 0xf, (color->blue >> 8)  & 0xf,
1895                                 (color->blue >>  4)  & 0xf, (color->blue)       & 0xf);
1896 }
1897
1898 gchar *
1899 modest_text_utils_text_buffer_get_text (GtkTextBuffer *buffer)
1900 {
1901         GtkTextIter start, end;
1902         gchar *slice, *current;
1903         GString *result = g_string_new ("");
1904
1905         g_return_val_if_fail (buffer && GTK_IS_TEXT_BUFFER (buffer), NULL);
1906         
1907         gtk_text_buffer_get_start_iter (buffer, &start);
1908         gtk_text_buffer_get_end_iter (buffer, &end);
1909
1910         slice = gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
1911         current = slice;
1912
1913         while (current && current != '\0') {
1914                 if (g_utf8_get_char (current) == 0xFFFC) {
1915                         result = g_string_append_c (result, ' ');
1916                         current = g_utf8_next_char (current);
1917                 } else {
1918                         gchar *next = g_utf8_strchr (current, -1, 0xFFFC);
1919                         if (next == NULL) {
1920                                 result = g_string_append (result, current);
1921                         } else {
1922                                 result = g_string_append_len (result, current, next - current);
1923                         }
1924                         current = next;
1925                 }
1926         }
1927         g_free (slice);
1928
1929         return g_string_free (result, FALSE);
1930         
1931 }
1932
1933 gboolean
1934 modest_text_utils_is_forbidden_char (const gchar character,
1935                                      ModestTextUtilsForbiddenCharType type)
1936 {
1937         gint i, len;
1938         const gchar *forbidden_chars = NULL;
1939         
1940         /* We need to get the length in the switch because the
1941            compiler needs to know the size at compile time */
1942         switch (type) {
1943         case ACCOUNT_TITLE_FORBIDDEN_CHARS:
1944                 forbidden_chars = account_title_forbidden_chars;
1945                 len = G_N_ELEMENTS (account_title_forbidden_chars);
1946                 break;
1947         case FOLDER_NAME_FORBIDDEN_CHARS:
1948                 forbidden_chars = folder_name_forbidden_chars;
1949                 len = G_N_ELEMENTS (folder_name_forbidden_chars);
1950                 break;
1951         case USER_NAME_FORBIDDEN_NAMES:
1952                 forbidden_chars = user_name_forbidden_chars;
1953                 len = G_N_ELEMENTS (user_name_forbidden_chars);
1954                 break;
1955         default:
1956                 g_return_val_if_reached (TRUE);
1957         }
1958
1959         for (i = 0; i < len ; i++)
1960                 if (forbidden_chars[i] == character)
1961                         return TRUE;
1962
1963         return FALSE; /* it's valid! */
1964 }
1965
1966 gchar *      
1967 modest_text_utils_label_get_selection (GtkLabel *label)
1968 {
1969         gint start, end;
1970         gchar *selection;
1971
1972         if (gtk_label_get_selection_bounds (GTK_LABEL (label), &start, &end)) {
1973                 const gchar *start_offset;
1974                 const gchar *end_offset;
1975                 start_offset = gtk_label_get_text (GTK_LABEL (label));
1976                 start_offset = g_utf8_offset_to_pointer (start_offset, start);
1977                 end_offset = gtk_label_get_text (GTK_LABEL (label));
1978                 end_offset = g_utf8_offset_to_pointer (end_offset, end);
1979                 selection = g_strndup (start_offset, end_offset - start_offset);
1980                 return selection;
1981         } else {
1982                 return g_strdup ("");
1983         }
1984 }
1985
1986 static gboolean
1987 _forward_search_image_char (gunichar ch,
1988                             gpointer userdata)
1989 {
1990         return (ch == 0xFFFC);
1991 }
1992
1993 gboolean
1994 modest_text_utils_buffer_selection_is_valid (GtkTextBuffer *buffer)
1995 {
1996         gboolean result;
1997         GtkTextIter start, end;
1998
1999         g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
2000
2001         result = gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (buffer));
2002
2003         /* check there are no images in selection */
2004         if (result) {
2005                 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
2006                 if (gtk_text_iter_get_char (&start)== 0xFFFC)
2007                         result = FALSE;
2008                 else {
2009                         gtk_text_iter_backward_char (&end);
2010                         if (gtk_text_iter_forward_find_char (&start, _forward_search_image_char,
2011                                                              NULL, &end))
2012                                 result = FALSE;
2013                 }
2014                                     
2015         }
2016
2017         return result;
2018 }
2019
2020 static void
2021 remove_quotes (gchar **quotes)
2022 {
2023         if (g_str_has_prefix (*quotes, "\"") && g_str_has_suffix (*quotes, "\"")) {
2024                 gchar *result;
2025                 result = g_strndup ((*quotes)+1, strlen (*quotes) - 2);
2026                 g_free (*quotes);
2027                 *quotes = result;
2028         }
2029 }
2030
2031 static void
2032 remove_extra_spaces (gchar *string)
2033 {
2034         gchar *start;
2035
2036         start = string;
2037         while (start && start[0] != '\0') {
2038                 if ((start[0] == ' ') && (start[1] == ' ')) {
2039                         g_strchug (start+1);
2040                 }
2041                 start++;
2042         }
2043 }
2044
2045 gchar *
2046 modest_text_utils_escape_mnemonics (const gchar *text)
2047 {
2048         const gchar *p;
2049         GString *result = NULL;
2050
2051         if (text == NULL)
2052                 return NULL;
2053
2054         result = g_string_new ("");
2055         for (p = text; *p != '\0'; p++) {
2056                 if (*p == '_')
2057                         result = g_string_append (result, "__");
2058                 else
2059                         result = g_string_append_c (result, *p);
2060         }
2061         
2062         return g_string_free (result, FALSE);
2063 }
2064
2065 gchar *
2066 modest_text_utils_simplify_recipients (const gchar *recipients)
2067 {
2068         GSList *addresses, *node;
2069         GString *result;
2070         gboolean is_first = TRUE;
2071
2072         if (recipients == NULL)
2073                 return g_strdup ("");
2074
2075         addresses = modest_text_utils_split_addresses_list (recipients);
2076         result = g_string_new ("");
2077
2078         for (node = addresses; node != NULL; node = g_slist_next (node)) {
2079                 const gchar *address = (const gchar *) node->data;
2080                 gchar *left_limit, *right_limit;
2081
2082                 left_limit = strstr (address, "<");
2083                 right_limit = g_strrstr (address, ">");
2084
2085                 if (is_first)
2086                         is_first = FALSE;
2087                 else
2088                         result = g_string_append (result, ", ");
2089
2090                 if ((left_limit == NULL)||(right_limit == NULL)|| (left_limit > right_limit)) {
2091                         result = g_string_append (result, address);
2092                 } else {
2093                         gchar *name_side;
2094                         gchar *email_side;
2095                         name_side = g_strndup (address, left_limit - address);
2096                         name_side = g_strstrip (name_side);
2097                         remove_quotes (&name_side);
2098                         email_side = get_email_from_address (address);
2099                         if (name_side && email_side && !strcmp (name_side, email_side)) {
2100                                 result = g_string_append (result, email_side);
2101                         } else {
2102                                 result = g_string_append (result, address);
2103                         }
2104                         g_free (name_side);
2105                         g_free (email_side);
2106                 }
2107
2108         }
2109         g_slist_foreach (addresses, (GFunc)g_free, NULL);
2110         g_slist_free (addresses);
2111
2112         return g_string_free (result, FALSE);
2113
2114 }
2115
2116 GSList *
2117 modest_text_utils_remove_duplicate_addresses_list (GSList *address_list)
2118 {
2119         GSList *new_list, *iter;
2120         GHashTable *table;
2121
2122         g_return_val_if_fail (address_list, NULL);
2123
2124         table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2125
2126         new_list = address_list;
2127         iter = address_list;
2128         while (iter) {
2129                 const gchar* address = (const gchar*)iter->data;
2130
2131                 /* We need only the email to just compare it and not
2132                    the full address which would make "a <a@a.com>"
2133                    different from "a@a.com" */
2134                 const gchar *email = get_email_from_address (address);
2135
2136                 /* ignore the address if already seen */
2137                 if (g_hash_table_lookup (table, email) == 0) {
2138                         g_hash_table_insert (table, (gchar*)email, GINT_TO_POINTER(1));
2139                         iter = g_slist_next (iter);
2140                 } else {
2141                         GSList *tmp = g_slist_next (iter);
2142                         new_list = g_slist_delete_link (new_list, iter);
2143                         iter = tmp;
2144                 }
2145         }
2146
2147         g_hash_table_unref (table);
2148
2149         return new_list;
2150 }
2151
2152 gchar *
2153 modest_text_utils_get_secure_header (const gchar *value,
2154                                      const gchar *header)
2155 {
2156         const gint max_len = 16384;
2157         gchar *new_value = NULL;
2158         gchar *needle = g_strrstr (value, header);
2159
2160         if (needle && value != needle)
2161                 new_value = g_strdup (needle + strlen (header));
2162
2163         if (!new_value)
2164                 new_value = g_strdup (value);
2165
2166         /* Do a max length check to prevent DoS attacks caused by huge
2167            malformed headers */
2168         if (g_utf8_validate (new_value, -1, NULL)) {
2169                 if (g_utf8_strlen (new_value, -1) > max_len) {
2170                         gchar *tmp = g_malloc0 (max_len * 4);
2171                         g_utf8_strncpy (tmp, (const gchar *) new_value, max_len);
2172                         g_free (new_value);
2173                         new_value = tmp;
2174                 }
2175         } else {
2176                 if (strlen (new_value) > max_len) {
2177                         gchar *tmp = g_malloc0 (max_len);
2178                         strncpy (new_value, tmp, max_len);
2179                         g_free (new_value);
2180                         new_value = tmp;
2181                 }
2182         }
2183
2184         return new_value;
2185 }
2186
2187 static gboolean
2188 is_quoted (const char *start, const gchar *end)
2189 {
2190         gchar *c;
2191
2192         c = (gchar *) start;
2193         while (*c == ' ')
2194                 c = g_utf8_next_char (c);
2195
2196         if (*c == '\0' || *c != '\"')
2197                 return FALSE;
2198
2199         c = (gchar *) end;
2200         while (*c == ' ' && c != start)
2201                 c = g_utf8_prev_char (c);
2202
2203         if (c == start || *c != '\"')
2204                 return FALSE;
2205
2206         return TRUE;
2207 }
2208
2209
2210 static void
2211 quote_name_part (GString **str, gchar **cur, gchar **start)
2212 {
2213         gchar *blank;
2214         gint str_len = *cur - *start;
2215
2216         while (**start == ' ') {
2217                 *start = g_utf8_next_char (*start);
2218                 str_len--;
2219         }
2220
2221         blank = g_utf8_strrchr (*start, str_len, g_utf8_get_char (" "));
2222         if (blank && (blank != *start)) {
2223                 if (is_quoted (*start, blank - 1)) {
2224                         *str = g_string_append_len (*str, *start, str_len);
2225                         *str = g_string_append (*str, ";");
2226                         *start = g_utf8_next_char (*cur);
2227                 } else {
2228                         *str = g_string_append_c (*str, '"');
2229                         *str = g_string_append_len (*str, *start, (blank - *start));
2230                         *str = g_string_append_c (*str, '"');
2231                         *str = g_string_append_len (*str, blank, (*cur - blank));
2232                         *str = g_string_append (*str, ";");
2233                         *start = g_utf8_next_char (*cur);
2234                 }
2235         } else {
2236                 *str = g_string_append_len (*str, *start, str_len);
2237                 *str = g_string_append (*str, ";");
2238                 *start = g_utf8_next_char (*cur);
2239         }
2240 }
2241
2242 gchar *
2243 modest_text_utils_quote_names (const gchar *recipients)
2244 {
2245         GString *str;
2246         gchar *start, *cur;
2247
2248         str = g_string_new ("");
2249         start = (gchar*) recipients;
2250         cur = (gchar*) recipients;
2251
2252         for (cur = start; *cur != '\0'; cur = g_utf8_next_char (cur)) {
2253                 if (*cur == ',' || *cur == ';') {
2254                         if (!g_utf8_strchr (start, (cur - start + 1), g_utf8_get_char ("@")))
2255                                 continue;
2256                         quote_name_part (&str, &cur, &start);
2257                 }
2258         }
2259
2260         quote_name_part (&str, &cur, &start);
2261
2262         return g_string_free (str, FALSE);
2263 }
2264
2265 /* Returns TRUE if there is no recipients in the text buffer. Note
2266    that strings like " ; , " contain only separators and thus no
2267    recipients */
2268 gboolean
2269 modest_text_utils_no_recipient (GtkTextBuffer *buffer)
2270 {
2271         gboolean retval = TRUE;
2272         gchar *text, *tmp;
2273         GtkTextIter start, end;
2274
2275         if (gtk_text_buffer_get_char_count (buffer) == 0)
2276                 return TRUE;
2277
2278         gtk_text_buffer_get_start_iter (buffer, &start);
2279         gtk_text_buffer_get_end_iter (buffer, &end);
2280
2281         text = g_strstrip (gtk_text_buffer_get_text (buffer, &start, &end, FALSE));
2282         if (!g_strcmp0 (text, ""))
2283                 return TRUE;
2284
2285         tmp = text;
2286         while (tmp && *tmp != '\0') {
2287                 if ((*tmp != ',') && (*tmp != ';') &&
2288                     (*tmp != '\r') && (*tmp != '\n') &&
2289                     (*tmp != ' ')) {
2290                         retval = FALSE;
2291                         break;
2292                 } else {
2293                         tmp++;
2294                 }
2295         }
2296         g_free (text);
2297
2298         return retval;
2299 }