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