* Fixes NB#86562, grab the keyboard focus for the new accounts wizard when no accoun...
[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
69 /*
70  * we need these regexps to find URLs in plain text e-mails
71  */
72 typedef struct _url_match_pattern_t url_match_pattern_t;
73 struct _url_match_pattern_t {
74         gchar   *regex;
75         regex_t *preg;
76         gchar   *prefix;
77 };
78
79 typedef struct _url_match_t url_match_t;
80 struct _url_match_t {
81         guint offset;
82         guint len;
83         const gchar* prefix;
84 };
85
86
87 /*
88  * we mark the ampersand with \007 when converting text->html
89  * because after text->html we do hyperlink detecting, which
90  * could be screwed up by the ampersand.
91  * ie. 1<3 ==> 1\007lt;3
92  */
93 #define MARK_AMP '\007'
94 #define MARK_AMP_STR "\007"
95
96 /* mark &amp; separately, because they are parts of urls.
97  * ie. a&b => a\006amp;b, but a>b => a\007gt;b
98  *
99  * we need to handle '&' separately, because it can be part of URIs
100  * (as in href="http://foo.bar?a=1&b=1"), so inside those URIs
101  * we need to re-replace \006amp; with '&' again, while outside uri's
102  * it will be '&amp;'
103  * 
104  * yes, it's messy, but a consequence of doing text->html first, then hyperlinkify
105  */
106 #define MARK_AMP_URI '\006'
107 #define MARK_AMP_URI_STR "\006"
108
109
110 /* note: match MARK_AMP_URI_STR as well, because after txt->html, a '&' will look like $(MARK_AMP_URI_STR)"amp;" */
111 #define MAIL_VIEWER_URL_MATCH_PATTERNS  {                               \
112         { "(file|rtsp|http|ftp|https|mms|mmsh|rtsp|rdp|lastfm)://[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR \
113                         "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",      \
114           NULL, NULL },\
115         { "www\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",\
116                         NULL, "http://" },                              \
117         { "ftp\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",\
118           NULL, "ftp://" },\
119         { "(jabberto|voipto|sipto|sip|chatto|xmpp):[-_a-z@0-9.+]+", \
120            NULL, NULL},                                             \
121         { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+",                    \
122           NULL, NULL},\
123         { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\
124           NULL, "mailto:"}\
125         }
126
127 const gchar account_title_forbidden_chars[] = {
128         '\\', '/', ':', '*', '?', '\'', '<', '>', '|', '^'
129 };
130 const gchar folder_name_forbidden_chars[] = {
131         '<', '>', ':', '\'', '/', '\\', '|', '?', '*', '^', '%', '$', '#', '&'
132 };
133 const gchar user_name_forbidden_chars[] = {
134         '<', '>'
135 };
136 const guint ACCOUNT_TITLE_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (account_title_forbidden_chars);
137 const guint FOLDER_NAME_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (folder_name_forbidden_chars);
138 const guint USER_NAME_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (user_name_forbidden_chars);
139
140 /* private */
141 static gchar*   cite                    (const time_t sent_date, const gchar *from);
142 static void     hyperlinkify_plain_text (GString *txt, gint offset);
143 static gint     cmp_offsets_reverse     (const url_match_t *match1, const url_match_t *match2);
144 static GSList*  get_url_matches         (GString *txt, gint offset);
145
146 static GString* get_next_line           (const char *b, const gsize blen, const gchar * iter);
147 static int      get_indent_level        (const char *l);
148 static void     unquote_line            (GString * l);
149 static void     append_quoted           (GString * buf, const int indent, const GString * str, 
150                                          const int cutpoint);
151 static int      get_breakpoint_utf8     (const gchar * s, const gint indent, const gint limit);
152 static int      get_breakpoint_ascii    (const gchar * s, const gint indent, const gint limit);
153 static int      get_breakpoint          (const gchar * s, const gint indent, const gint limit);
154
155 static gchar*   modest_text_utils_quote_plain_text (const gchar *text, 
156                                                     const gchar *cite, 
157                                                     const gchar *signature,
158                                                     GList *attachments, 
159                                                     int limit);
160
161 static gchar*   modest_text_utils_quote_html       (const gchar *text, 
162                                                     const gchar *cite,
163                                                     const gchar *signature,
164                                                     GList *attachments,
165                                                     int limit);
166 static gchar*   get_email_from_address (const gchar *address);
167
168
169 /* ******************************************************************* */
170 /* ************************* PUBLIC FUNCTIONS ************************ */
171 /* ******************************************************************* */
172
173 gchar *
174 modest_text_utils_quote (const gchar *text, 
175                          const gchar *content_type,
176                          const gchar *signature,
177                          const gchar *from,
178                          const time_t sent_date, 
179                          GList *attachments,
180                          int limit)
181 {
182         gchar *retval, *cited;
183
184         g_return_val_if_fail (text, NULL);
185         g_return_val_if_fail (content_type, NULL);
186
187         cited = cite (sent_date, from);
188         
189         if (content_type && strcmp (content_type, "text/html") == 0)
190                 /* TODO: extract the <body> of the HTML and pass it to
191                    the function */
192                 retval = modest_text_utils_quote_html (text, cited, signature, attachments, limit);
193         else
194                 retval = modest_text_utils_quote_plain_text (text, cited, signature, attachments, limit);
195         
196         g_free (cited);
197         
198         return retval;
199 }
200
201
202 gchar *
203 modest_text_utils_cite (const gchar *text,
204                         const gchar *content_type,
205                         const gchar *signature,
206                         const gchar *from,
207                         time_t sent_date)
208 {
209         gchar *retval;
210         gchar *tmp_sig;
211         
212         g_return_val_if_fail (text, NULL);
213         g_return_val_if_fail (content_type, NULL);
214         
215         if (!signature)
216                 retval = g_strdup ("");
217         else if (strcmp(content_type, "text/html") == 0) {
218                 tmp_sig = g_strconcat ("\n", signature, NULL);
219                 retval = modest_text_utils_convert_to_html_body(tmp_sig, -1, TRUE);
220                 g_free (tmp_sig);
221         } else {
222                 retval = g_strconcat (text, "\n", signature, NULL);
223         }
224
225         return retval;
226 }
227
228 static gchar *
229 forward_cite (const gchar *from,
230               const gchar *sent,
231               const gchar *to,
232               const gchar *subject)
233 {
234         g_return_val_if_fail (sent, NULL);
235         
236         return g_strdup_printf ("%s\n%s %s\n%s %s\n%s %s\n%s %s\n", 
237                                 FORWARD_STRING, 
238                                 FROM_STRING, (from)?from:"",
239                                 SENT_STRING, sent,
240                                 TO_STRING, (to)?to:"",
241                                 SUBJECT_STRING, (subject)?subject:"");
242 }
243
244 gchar * 
245 modest_text_utils_inline (const gchar *text,
246                           const gchar *content_type,
247                           const gchar *signature,
248                           const gchar *from,
249                           time_t sent_date,
250                           const gchar *to,
251                           const gchar *subject)
252 {
253         gchar sent_str[101];
254         gchar *cited;
255         gchar *retval;
256         
257         g_return_val_if_fail (text, NULL);
258         g_return_val_if_fail (content_type, NULL);
259         
260         modest_text_utils_strftime (sent_str, 100, "%c", sent_date);
261
262         cited = forward_cite (from, sent_str, to, subject);
263         
264         if (content_type && strcmp (content_type, "text/html") == 0)
265                 retval = modest_text_utils_quote_html (text, cited, signature, NULL, 80);
266         else
267                 retval = modest_text_utils_quote_plain_text (text, cited, signature, NULL, 80);
268         
269         g_free (cited);
270         return retval;
271 }
272
273 /* just to prevent warnings:
274  * warning: `%x' yields only last 2 digits of year in some locales
275  */
276 gsize
277 modest_text_utils_strftime(char *s, gsize max, const char *fmt, time_t timet)
278 {
279         struct tm tm;
280
281         /* does not work on old maemo glib: 
282          *   g_date_set_time_t (&date, timet);
283          */
284         localtime_r (&timet, &tm);
285         return strftime(s, max, fmt, &tm);
286 }
287
288 gchar *
289 modest_text_utils_derived_subject (const gchar *subject, const gchar *prefix)
290 {
291         gchar *tmp;
292
293         g_return_val_if_fail (prefix, NULL);
294
295         if (!subject || subject[0] == '\0')
296                 subject = _("mail_va_no_subject");
297
298         tmp = g_strchug (g_strdup (subject));
299
300         if (!strncmp (tmp, prefix, strlen (prefix))) {
301                 return tmp;
302         } else {
303                 g_free (tmp);
304                 return g_strdup_printf ("%s %s", prefix, subject);
305         }
306 }
307
308 gchar*
309 modest_text_utils_remove_address (const gchar *address_list, const gchar *address)
310 {
311         gchar *dup, *token, *ptr = NULL, *result;
312         GString *filtered_emails;
313         gchar *email_address;
314
315         g_return_val_if_fail (address_list, NULL);
316         
317         if (!address)
318                 return g_strdup (address_list);
319
320         email_address = get_email_from_address (address);
321         
322         /* search for substring */
323         if (!strstr ((const char *) address_list, (const char *) email_address)) {
324                 g_free (email_address);
325                 return g_strdup (address_list);
326         }
327
328         dup = g_strdup (address_list);
329         filtered_emails = g_string_new (NULL);
330         
331         token = strtok_r (dup, ",", &ptr);
332
333         while (token != NULL) {
334                 /* Add to list if not found */
335                 if (!strstr ((const char *) token, (const char *) email_address)) {
336                         if (filtered_emails->len == 0)
337                                 g_string_append_printf (filtered_emails, "%s", g_strstrip (token));
338                         else
339                                 g_string_append_printf (filtered_emails, ",%s", g_strstrip (token));
340                 }
341                 token = strtok_r (NULL, ",", &ptr);
342         }
343         result = filtered_emails->str;
344
345         /* Clean */
346         g_free (email_address);
347         g_free (dup);
348         g_string_free (filtered_emails, FALSE);
349
350         return result;
351 }
352
353
354 gchar*
355 modest_text_utils_remove_duplicate_addresses (const gchar *address_list)
356 {
357         GSList *addresses, *cursor;
358         GHashTable *table;
359         gchar *new_list = NULL;
360         
361         g_return_val_if_fail (address_list, NULL);
362
363         table = g_hash_table_new (g_str_hash, g_str_equal);
364         addresses = modest_text_utils_split_addresses_list (address_list);
365
366         cursor = addresses;
367         while (cursor) {
368                 const gchar* address = (const gchar*)cursor->data;
369
370                 /* ignore the address if already seen */
371                 if (g_hash_table_lookup (table, address) == 0) {
372                         gchar *tmp;
373
374                         if (!new_list) {
375                                 tmp = g_strdup (address);
376                         } else {
377                                 tmp = g_strjoin (",", new_list, address, NULL);
378                                 g_free (new_list);
379                         }
380                         new_list = tmp;
381                         
382                         g_hash_table_insert (table, (gchar*)address, GINT_TO_POINTER(1));
383                 }
384                 cursor = g_slist_next (cursor);
385         }
386
387         g_hash_table_destroy (table);
388         g_slist_foreach (addresses, (GFunc)g_free, NULL);
389         g_slist_free (addresses);
390
391         return new_list;
392 }
393
394
395 static void
396 modest_text_utils_convert_buffer_to_html_start (GString *html, const gchar *data, gssize n)
397 {
398         guint           i;
399         gboolean        space_seen = FALSE;
400         guint           break_dist = 0; /* distance since last break point */
401
402         if (n == -1)
403                 n = strlen (data);
404
405         /* replace with special html chars where needed*/
406         for (i = 0; i != n; ++i)  {
407                 guchar kar = data[i];
408                 
409                 if (space_seen && kar != ' ') {
410                         g_string_append_c (html, ' ');
411                         space_seen = FALSE;
412                 }
413                 
414                 /* we artificially insert a breakpoint (newline)
415                  * after 256, to make sure our lines are not so long
416                  * they will DOS the regexping later
417                  * Also, check that kar is ASCII to make sure that we
418                  * don't break a UTF8 char in two
419                  */
420                 if (++break_dist >= 256 && kar < 127) {
421                         g_string_append_c (html, '\n');
422                         break_dist = 0;
423                 }
424                 
425                 switch (kar) {
426                 case 0:
427                 case MARK_AMP:
428                 case MARK_AMP_URI:      
429                         /* this is a temp place holder for '&'; we can only
430                                 * set the real '&' after hyperlink translation, otherwise
431                                 * we might screw that up */
432                         break; /* ignore embedded \0s and MARK_AMP */   
433                 case '<'  : g_string_append (html, MARK_AMP_STR "lt;");   break;
434                 case '>'  : g_string_append (html, MARK_AMP_STR "gt;");   break;
435                 case '&'  : g_string_append (html, MARK_AMP_URI_STR "amp;");  break; /* special case */
436                 case '"'  : g_string_append (html, MARK_AMP_STR "quot;");  break;
437
438                 /* don't convert &apos; --> wpeditor will try to re-convert it... */    
439                 //case '\'' : g_string_append (html, "&apos;"); break;
440                 case '\n' : g_string_append (html, "<br>\n");break_dist= 0; break;
441                 case '\t' : g_string_append (html, MARK_AMP_STR "nbsp;" MARK_AMP_STR "nbsp;" MARK_AMP_STR "nbsp; ");
442                         break_dist=0; break; /* note the space at the end*/
443                 case ' ':
444                         break_dist = 0;
445                         if (space_seen) { /* second space in a row */
446                                 g_string_append (html, "&nbsp; ");
447                                 space_seen = FALSE;
448                         } else
449                                 space_seen = TRUE;
450                         break;
451                 default:
452                         g_string_append_c (html, kar);
453                 }
454         }
455 }
456
457
458 static void
459 modest_text_utils_convert_buffer_to_html_finish (GString *html)
460 {
461         int i;
462         /* replace all our MARK_AMPs with real ones */
463         for (i = 0; i != html->len; ++i)
464                 if ((html->str)[i] == MARK_AMP || (html->str)[i] == MARK_AMP_URI)
465                         (html->str)[i] = '&';
466 }
467
468
469 gchar*
470 modest_text_utils_convert_to_html (const gchar *data)
471 {
472         GString         *html;      
473         gsize           len;
474
475         g_return_val_if_fail (data, NULL);
476         
477         if (!data)
478                 return NULL;
479
480         len = strlen (data);
481         html = g_string_sized_new (1.5 * len);  /* just a  guess... */
482
483         g_string_append_printf (html,
484                                 "<html><head>"
485                                 "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf8\">"
486                                 "</head>"
487                                 "<body>");
488
489         modest_text_utils_convert_buffer_to_html_start (html, data, -1);
490         
491         g_string_append (html, "</body></html>");
492
493         if (len <= HYPERLINKIFY_MAX_LENGTH)
494                 hyperlinkify_plain_text (html, 0);
495
496         modest_text_utils_convert_buffer_to_html_finish (html);
497         
498         return g_string_free (html, FALSE);
499 }
500
501 gchar *
502 modest_text_utils_convert_to_html_body (const gchar *data, gssize n, gboolean hyperlinkify)
503 {
504         GString         *html;      
505
506         g_return_val_if_fail (data, NULL);
507
508         if (!data)
509                 return NULL;
510
511         if (n == -1) 
512                 n = strlen (data);
513         html = g_string_sized_new (1.5 * n);    /* just a  guess... */
514
515         modest_text_utils_convert_buffer_to_html_start (html, data, n);
516
517         if (hyperlinkify && (n < HYPERLINKIFY_MAX_LENGTH))
518                 hyperlinkify_plain_text (html, 0);
519
520         modest_text_utils_convert_buffer_to_html_finish (html);
521         
522         return g_string_free (html, FALSE);
523 }
524
525 void
526 modest_text_utils_get_addresses_indexes (const gchar *addresses, GSList **start_indexes, GSList **end_indexes)
527 {
528         gchar *current, *start, *last_blank;
529         gint start_offset = 0, current_offset = 0;
530
531         g_return_if_fail (start_indexes != NULL);
532         g_return_if_fail (end_indexes != NULL);
533
534         start = (gchar *) addresses;
535         current = start;
536         last_blank = start;
537
538         while (*current != '\0') {
539                 if ((start == current)&&((*current == ' ')||(*current == ',')||(*current == ';'))) {
540                         start = g_utf8_next_char (start);
541                         start_offset++;
542                         last_blank = current;
543                 } else if ((*current == ',')||(*current == ';')) {
544                         gint *start_index, *end_index;
545                         start_index = g_new0(gint, 1);
546                         end_index = g_new0(gint, 1);
547                         *start_index = start_offset;
548                         *end_index = current_offset;
549                         *start_indexes = g_slist_prepend (*start_indexes, start_index);
550                         *end_indexes = g_slist_prepend (*end_indexes, end_index);
551                         start = g_utf8_next_char (current);
552                         start_offset = current_offset + 1;
553                         last_blank = start;
554                 } else if (*current == '"') {
555                         current = g_utf8_next_char (current);
556                         current_offset ++;
557                         while ((*current != '"')&&(*current != '\0')) {
558                                 current = g_utf8_next_char (current);
559                                 current_offset ++;
560                         }
561                 }
562                                 
563                 current = g_utf8_next_char (current);
564                 current_offset ++;
565         }
566
567         if (start != current) {
568                         gint *start_index, *end_index;
569                         start_index = g_new0(gint, 1);
570                         end_index = g_new0(gint, 1);
571                         *start_index = start_offset;
572                         *end_index = current_offset;
573                         *start_indexes = g_slist_prepend (*start_indexes, start_index);
574                         *end_indexes = g_slist_prepend (*end_indexes, end_index);
575         }
576         
577         *start_indexes = g_slist_reverse (*start_indexes);
578         *end_indexes = g_slist_reverse (*end_indexes);
579
580         return;
581 }
582
583
584 GSList *
585 modest_text_utils_split_addresses_list (const gchar *addresses)
586 {
587         GSList *head;
588         const gchar *my_addrs = addresses;
589         const gchar *end;
590         gchar *addr;
591
592         g_return_val_if_fail (addresses, NULL);
593         
594         /* skip any space, ',', ';' at the start */
595         while (my_addrs && (my_addrs[0] == ' ' || my_addrs[0] == ',' || my_addrs[0] == ';'))
596                ++my_addrs;
597
598         /* are we at the end of addresses list? */
599         if (!my_addrs[0])
600                 return NULL;
601         
602         /* nope, we are at the start of some address
603          * now, let's find the end of the address */
604         end = my_addrs + 1;
605         while (end[0] && end[0] != ',' && end[0] != ';')
606                 ++end;
607
608         /* we got the address; copy it and remove trailing whitespace */
609         addr = g_strndup (my_addrs, end - my_addrs);
610         g_strchomp (addr);
611
612         head = g_slist_append (NULL, addr);
613         head->next = modest_text_utils_split_addresses_list (end); /* recurse */
614
615         return head;
616 }
617
618
619 void
620 modest_text_utils_address_range_at_position (const gchar *recipients_list,
621                                              guint position,
622                                              guint *start,
623                                              guint *end)
624 {
625         gchar *current = NULL;
626         gint range_start = 0;
627         gint range_end = 0;
628         gint index;
629         gboolean is_quoted = FALSE;
630
631         g_return_if_fail (recipients_list);
632         g_return_if_fail (position < g_utf8_strlen(recipients_list, -1));
633                 
634         index = 0;
635         for (current = (gchar *) recipients_list; *current != '\0';
636              current = g_utf8_find_next_char (current, NULL)) {
637                 gunichar c = g_utf8_get_char (current);
638
639                 if ((c == ',') && (!is_quoted)) {
640                         if (index < position) {
641                                 range_start = index + 1;
642                         } else {
643                                 break;
644                         }
645                 } else if (c == '\"') {
646                         is_quoted = !is_quoted;
647                 } else if ((c == ' ') &&(range_start == index)) {
648                         range_start ++;
649                 }
650                 index ++;
651                 range_end = index;
652         }
653
654         if (start)
655                 *start = range_start;
656         if (end)
657                 *end = range_end;
658 }
659
660 gchar *
661 modest_text_utils_address_with_standard_length (const gchar *recipients_list)
662 {
663         gchar ** splitted;
664         gchar ** current;
665         GString *buffer = g_string_new ("");
666
667         splitted = g_strsplit (recipients_list, "\n", 0);
668         current = splitted;
669         while (*current) {
670                 gchar *line;
671                 if (current != splitted)
672                         buffer = g_string_append_c (buffer, '\n');
673                 line = g_strndup (*splitted, 1000);
674                 buffer = g_string_append (buffer, line);
675                 g_free (line);
676                 current++;
677         }
678
679         g_strfreev (splitted);
680
681         return g_string_free (buffer, FALSE);
682 }
683
684
685 /* ******************************************************************* */
686 /* ************************* UTILIY FUNCTIONS ************************ */
687 /* ******************************************************************* */
688
689 static GString *
690 get_next_line (const gchar * b, const gsize blen, const gchar * iter)
691 {
692         GString *gs;
693         const gchar *i0;
694         
695         if (iter > b + blen)
696                 return g_string_new("");
697         
698         i0 = iter;
699         while (iter[0]) {
700                 if (iter[0] == '\n')
701                         break;
702                 iter++;
703         }
704         gs = g_string_new_len (i0, iter - i0);
705         return gs;
706 }
707 static int
708 get_indent_level (const char *l)
709 {
710         int indent = 0;
711
712         while (l[0]) {
713                 if (l[0] == '>') {
714                         indent++;
715                         if (l[1] == ' ') {
716                                 l++;
717                         }
718                 } else {
719                         break;
720                 }
721                 l++;
722
723         }
724
725         /*      if we hit the signature marker "-- ", we return -(indent + 1). This
726          *      stops reformatting.
727          */
728         if (strcmp (l, "-- ") == 0) {
729                 return -1 - indent;
730         } else {
731                 return indent;
732         }
733 }
734
735 static void
736 unquote_line (GString * l)
737 {
738         gchar *p;
739
740         p = l->str;
741         while (p[0]) {
742                 if (p[0] == '>') {
743                         if (p[1] == ' ') {
744                                 p++;
745                         }
746                 } else {
747                         break;
748                 }
749                 p++;
750         }
751         g_string_erase (l, 0, p - l->str);
752 }
753
754 static void
755 append_quoted (GString * buf, int indent, const GString * str,
756                const int cutpoint)
757 {
758         int i;
759
760         indent = indent < 0 ? abs (indent) - 1 : indent;
761         for (i = 0; i <= indent; i++) {
762                 g_string_append (buf, "> ");
763         }
764         if (cutpoint > 0) {
765                 g_string_append_len (buf, str->str, cutpoint);
766         } else {
767                 g_string_append (buf, str->str);
768         }
769         g_string_append (buf, "\n");
770 }
771
772 static int
773 get_breakpoint_utf8 (const gchar * s, gint indent, const gint limit)
774 {
775         gint index = 0;
776         const gchar *pos, *last;
777         gunichar *uni;
778
779         indent = indent < 0 ? abs (indent) - 1 : indent;
780
781         last = NULL;
782         pos = s;
783         uni = g_utf8_to_ucs4_fast (s, -1, NULL);
784         while (pos[0]) {
785                 if ((index + 2 * indent > limit) && last) {
786                         g_free (uni);
787                         return last - s;
788                 }
789                 if (g_unichar_isspace (uni[index])) {
790                         last = pos;
791                 }
792                 pos = g_utf8_next_char (pos);
793                 index++;
794         }
795         g_free (uni);
796         return strlen (s);
797 }
798
799 static int
800 get_breakpoint_ascii (const gchar * s, const gint indent, const gint limit)
801 {
802         gint i, last;
803
804         last = strlen (s);
805         if (last + 2 * indent < limit)
806                 return last;
807
808         for (i = strlen (s); i > 0; i--) {
809                 if (s[i] == ' ') {
810                         if (i + 2 * indent <= limit) {
811                                 return i;
812                         } else {
813                                 last = i;
814                         }
815                 }
816         }
817         return last;
818 }
819
820 static int
821 get_breakpoint (const gchar * s, const gint indent, const gint limit)
822 {
823
824         if (g_utf8_validate (s, -1, NULL)) {
825                 return get_breakpoint_utf8 (s, indent, limit);
826         } else {                /* assume ASCII */
827                 //g_warning("invalid UTF-8 in msg");
828                 return get_breakpoint_ascii (s, indent, limit);
829         }
830 }
831
832 static gchar *
833 cite (const time_t sent_date, const gchar *from)
834 {
835         return g_strdup (_("mcen_ia_editor_original_message"));
836 }
837
838 static gchar *
839 quoted_attachments (GList *attachments)
840 {
841         GList *node = NULL;
842         GString *result = g_string_new ("");
843         for (node = attachments; node != NULL; node = g_list_next (node)) {
844                 gchar *filename = (gchar *) node->data;
845                 g_string_append_printf ( result, "%s %s\n", _("mcen_ia_editor_attach_filename"), filename);
846         }
847
848         return g_string_free (result, FALSE);
849
850 }
851
852 static gchar *
853 modest_text_utils_quote_plain_text (const gchar *text, 
854                                     const gchar *cite, 
855                                     const gchar *signature,
856                                     GList *attachments,
857                                     int limit)
858 {
859         const gchar *iter;
860         gint indent, breakpoint, rem_indent = 0;
861         GString *q, *l, *remaining;
862         gsize len;
863         gchar *attachments_string = NULL;
864
865         q = g_string_new ("\n");
866         if (signature != NULL) {
867                 q = g_string_append (q, signature);
868                 q = g_string_append_c (q, '\n');
869         }
870         q = g_string_append (q, cite);
871         q = g_string_append_c (q, '\n');
872
873         /* remaining will store the rest of the line if we have to break it */
874         remaining = g_string_new ("");
875
876         iter = text;
877         len = strlen(text);
878         do {
879                 l = get_next_line (text, len, iter);
880                 iter = iter + l->len + 1;
881                 indent = get_indent_level (l->str);
882                 unquote_line (l);
883
884                 if (remaining->len) {
885                         if (l->len && indent == rem_indent) {
886                                 g_string_prepend (l, " ");
887                                 g_string_prepend (l, remaining->str);
888                         } else {
889                                 do {
890                                         breakpoint =
891                                                 get_breakpoint (remaining->str,
892                                                                 rem_indent,
893                                                                 limit);
894                                         append_quoted (q, rem_indent,
895                                                        remaining, breakpoint);
896                                         g_string_erase (remaining, 0,
897                                                         breakpoint);
898                                         if (remaining->str[0] == ' ') {
899                                                 g_string_erase (remaining, 0,
900                                                                 1);
901                                         }
902                                 } while (remaining->len);
903                         }
904                 }
905                 g_string_free (remaining, TRUE);
906                 breakpoint = get_breakpoint (l->str, indent, limit);
907                 remaining = g_string_new (l->str + breakpoint);
908                 if (remaining->str[0] == ' ') {
909                         g_string_erase (remaining, 0, 1);
910                 }
911                 rem_indent = indent;
912                 append_quoted (q, indent, l, breakpoint);
913                 g_string_free (l, TRUE);
914         } while ((iter < text + len) || (remaining->str[0]));
915
916         attachments_string = quoted_attachments (attachments);
917         q = g_string_append (q, attachments_string);
918         g_free (attachments_string);
919
920         return g_string_free (q, FALSE);
921 }
922
923 static gchar*
924 modest_text_utils_quote_html (const gchar *text, 
925                               const gchar *cite, 
926                               const gchar *signature,
927                               GList *attachments,
928                               int limit)
929 {
930         gchar *result = NULL;
931         gchar *signature_result = NULL;
932         const gchar *format = \
933                 "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" \
934                 "<html>\n" \
935                 "<body>\n" \
936                 "<br/>%s<br/>" \
937                 "<pre>%s<br/>%s<br/>%s</pre>\n" \
938                 "</body>\n" \
939                 "</html>\n";
940         gchar *attachments_string = NULL;
941         gchar *q_attachments_string = NULL;
942         gchar *q_cite = NULL;
943         gchar *html_text = NULL;
944
945         if (signature == NULL)
946                 signature_result = g_strdup ("");
947         else
948                 signature_result = modest_text_utils_convert_to_html_body (signature, -1, TRUE);
949
950         attachments_string = quoted_attachments (attachments);
951         q_attachments_string = modest_text_utils_convert_to_html_body (attachments_string, -1, TRUE);
952         q_cite = modest_text_utils_convert_to_html_body (cite, -1, TRUE);
953         html_text = modest_text_utils_convert_to_html_body (text, -1, TRUE);
954         result = g_strdup_printf (format, signature_result, q_cite, html_text, q_attachments_string);
955         g_free (q_cite);
956         g_free (html_text);
957         g_free (attachments_string);
958         g_free (q_attachments_string);
959         g_free (signature_result);
960         
961         return result;
962 }
963
964 static gint 
965 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
966 {
967         return match2->offset - match1->offset;
968 }
969
970 static gboolean url_matches_block = 0;
971 static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
972
973
974 static gboolean
975 compile_patterns ()
976 {
977         guint i;
978         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
979         for (i = 0; i != pattern_num; ++i) {
980                 patterns[i].preg = g_slice_new0 (regex_t);
981                 
982                 /* this should not happen */
983                 if (regcomp (patterns[i].preg, patterns[i].regex,
984                              REG_ICASE|REG_EXTENDED|REG_NEWLINE) != 0) {
985                         g_warning ("%s: error in regexp:\n%s\n", __FUNCTION__, patterns[i].regex);
986                         return FALSE;
987                 }
988         }
989         return TRUE;
990 }
991
992 static void 
993 free_patterns ()
994 {
995         guint i;
996         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
997         for (i = 0; i != pattern_num; ++i) {
998                 regfree (patterns[i].preg);
999                 g_slice_free  (regex_t, patterns[i].preg);
1000         } /* don't free patterns itself -- it's static */
1001 }
1002
1003 void
1004 modest_text_utils_hyperlinkify_begin (void)
1005 {
1006         if (url_matches_block == 0)
1007                 compile_patterns ();
1008         url_matches_block ++;
1009 }
1010
1011 void
1012 modest_text_utils_hyperlinkify_end (void)
1013 {
1014         url_matches_block--;
1015         if (url_matches_block <= 0)
1016                 free_patterns ();
1017 }
1018
1019
1020 static GSList*
1021 get_url_matches (GString *txt, gint offset)
1022 {
1023         regmatch_t rm;
1024         guint rv, i, tmp_offset = 0;
1025         GSList *match_list = NULL;
1026
1027         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
1028
1029         /* initalize the regexps */
1030         modest_text_utils_hyperlinkify_begin ();
1031
1032         /* find all the matches */
1033         for (i = 0; i != pattern_num; ++i) {
1034                 tmp_offset     = offset;        
1035                 while (1) {
1036                         url_match_t *match;
1037                         gboolean is_submatch;
1038                         GSList *cursor;
1039                         
1040                         if ((rv = regexec (patterns[i].preg, txt->str + tmp_offset, 1, &rm, 0)) != 0) {
1041                                 g_return_val_if_fail (rv == REG_NOMATCH, NULL); /* this should not happen */
1042                                 break; /* try next regexp */ 
1043                         }
1044                         if (rm.rm_so == -1)
1045                                 break;
1046                         
1047                         is_submatch = FALSE;
1048                         /* check  old matches to see if this has already been matched */
1049                         cursor = match_list;
1050                         while (cursor && !is_submatch) {
1051                                 const url_match_t *old_match =
1052                                         (const url_match_t *) cursor->data;
1053                                 guint new_offset = tmp_offset + rm.rm_so;
1054                                 is_submatch = (new_offset >  old_match->offset &&
1055                                                new_offset <  old_match->offset + old_match->len);
1056                                 cursor = g_slist_next (cursor);
1057                         }
1058
1059                         if (!is_submatch) {
1060                                 /* make a list of our matches (<offset, len, prefix> tupels)*/
1061                                 match = g_slice_new (url_match_t);
1062                                 match->offset = tmp_offset + rm.rm_so;
1063                                 match->len    = rm.rm_eo - rm.rm_so;
1064                                 match->prefix = patterns[i].prefix;
1065                                 match_list = g_slist_prepend (match_list, match);
1066                         }               
1067                         tmp_offset += rm.rm_eo;
1068                 }
1069         }
1070
1071         modest_text_utils_hyperlinkify_end ();
1072         
1073         /* now sort the list, so the matches are in reverse order of occurence.
1074          * that way, we can do the replacements starting from the end, so we don't need
1075          * to recalculate the offsets
1076          */
1077         match_list = g_slist_sort (match_list,
1078                                    (GCompareFunc)cmp_offsets_reverse); 
1079         return match_list;      
1080 }
1081
1082
1083
1084 /* replace all occurences of needle in haystack with repl*/
1085 static gchar*
1086 replace_string (const gchar *haystack, const gchar *needle, gchar repl)
1087 {
1088         gchar *str, *cursor;
1089
1090         if (!haystack || !needle || strlen(needle) == 0)
1091                 return haystack ? g_strdup(haystack) : NULL;
1092         
1093         str = g_strdup (haystack);
1094
1095         for (cursor = str; cursor && *cursor; ++cursor) {
1096                 if (g_str_has_prefix (cursor, needle)) {
1097                         cursor[0] = repl;
1098                         memmove (cursor + 1,
1099                                  cursor + strlen (needle),
1100                                  strlen (cursor + strlen (needle)) + 1);
1101                 }
1102         }
1103         
1104         return str;
1105 }
1106
1107 static void
1108 hyperlinkify_plain_text (GString *txt, gint offset)
1109 {
1110         GSList *cursor;
1111         GSList *match_list = get_url_matches (txt, offset);
1112
1113         /* we will work backwards, so the offsets stay valid */
1114         for (cursor = match_list; cursor; cursor = cursor->next) {
1115
1116                 url_match_t *match = (url_match_t*) cursor->data;
1117                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
1118                 gchar *repl = NULL; /* replacement  */
1119
1120                 /* the string still contains $(MARK_AMP_URI_STR)"amp;" for each
1121                  * '&' in the original, because of the text->html conversion.
1122                  * in the href-URL (and only there), we must convert that back to
1123                  * '&'
1124                  */
1125                 gchar *href_url = replace_string (url, MARK_AMP_URI_STR "amp;", '&');
1126                 
1127                 /* the prefix is NULL: use the one that is already there */
1128                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
1129                                         match->prefix ? match->prefix : EMPTY_STRING, 
1130                                         href_url, url);
1131
1132                 /* replace the old thing with our hyperlink
1133                  * replacement thing */
1134                 g_string_erase  (txt, match->offset, match->len);
1135                 g_string_insert (txt, match->offset, repl);
1136                 
1137                 g_free (url);
1138                 g_free (repl);
1139                 g_free (href_url);
1140
1141                 g_slice_free (url_match_t, match);      
1142         }
1143         
1144         g_slist_free (match_list);
1145 }
1146
1147 void
1148 modest_text_utils_hyperlinkify (GString *string_buffer)
1149 {
1150         gchar *after_body;
1151         gint offset = 0;
1152
1153         after_body = strstr (string_buffer->str, "<body>");
1154         if (after_body != NULL)
1155                 offset = after_body - string_buffer->str;
1156         hyperlinkify_plain_text (string_buffer, offset);
1157 }
1158
1159
1160 /* for optimization reasons, we change the string in-place */
1161 void
1162 modest_text_utils_get_display_address (gchar *address)
1163 {
1164         int i;
1165
1166         g_return_if_fail (address);
1167         
1168         if (!address)
1169                 return;
1170         
1171         /* should not be needed, and otherwise, we probably won't screw up the address
1172          * more than it already is :) 
1173          * g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL);
1174          * */
1175         
1176         /* remove leading whitespace */
1177         if (address[0] == ' ')
1178                 g_strchug (address);
1179                 
1180         for (i = 0; address[i]; ++i) {
1181                 if (address[i] == '<') {
1182                         if (G_UNLIKELY(i == 0))
1183                                 return; /* there's nothing else, leave it */
1184                         else {
1185                                 address[i] = '\0'; /* terminate the string here */
1186                                 return;
1187                         }
1188                 }
1189         }
1190 }
1191
1192
1193
1194
1195
1196 gchar *
1197 modest_text_utils_get_email_address (const gchar *full_address)
1198 {
1199         const gchar *left, *right;
1200
1201         g_return_val_if_fail (full_address, NULL);
1202         
1203         if (!full_address)
1204                 return NULL;
1205         
1206         g_return_val_if_fail (g_utf8_validate (full_address, -1, NULL), NULL);
1207         
1208         left = g_strrstr_len (full_address, strlen(full_address), "<");
1209         if (left == NULL)
1210                 return g_strdup (full_address);
1211
1212         right = g_strstr_len (left, strlen(left), ">");
1213         if (right == NULL)
1214                 return g_strdup (full_address);
1215
1216         return g_strndup (left + 1, right - left - 1);
1217 }
1218
1219 gint 
1220 modest_text_utils_get_subject_prefix_len (const gchar *sub)
1221 {
1222         gint prefix_len = 0;    
1223
1224         g_return_val_if_fail (sub, 0);
1225
1226         if (!sub)
1227                 return 0;
1228         
1229         /* optimization: "Re", "RE", "re","Fwd", "FWD", "fwd","FW","Fw", "fw" */
1230         if (sub[0] != 'R' && sub[0] != 'F' && sub[0] != 'r' && sub[0] != 'f')
1231                 return 0;
1232         else if (sub[0] && sub[1] != 'e' && sub[1] != 'E' && sub[1] != 'w' && sub[1] != 'W')
1233                 return 0;
1234
1235         prefix_len = 2;
1236         if (sub[2] == 'd')
1237                 ++prefix_len;
1238
1239         /* skip over a [...] block */
1240         if (sub[prefix_len] == '[') {
1241                 int c = prefix_len + 1;
1242                 while (sub[c] && sub[c] != ']')
1243                         ++c;
1244                 if (sub[c])
1245                         return 0; /* no end to the ']' found */
1246                 else
1247                         prefix_len = c + 1;
1248         }
1249
1250         /* did we find the ':' ? */
1251         if (sub[prefix_len] == ':') {
1252                 ++prefix_len;
1253                 if (sub[prefix_len] == ' ')
1254                         ++prefix_len;
1255                 prefix_len += modest_text_utils_get_subject_prefix_len (sub + prefix_len);
1256 /*              g_warning ("['%s','%s']", sub, (char*) sub + prefix_len); */
1257                 return prefix_len;
1258         } else
1259                 return 0;
1260 }
1261
1262
1263 gint
1264 modest_text_utils_utf8_strcmp (const gchar* s1, const gchar *s2, gboolean insensitive)
1265 {
1266
1267 /* work even when s1 and/or s2 == NULL */
1268         if (G_UNLIKELY(s1 == s2))
1269                 return 0;
1270         if (G_UNLIKELY(!s1))
1271                 return -1;
1272         if (G_UNLIKELY(!s2))
1273                 return 1;
1274         
1275         /* if it's not case sensitive */
1276         if (!insensitive) {
1277
1278                 /* optimization: shortcut if first char is ascii */ 
1279                 if (((s1[0] & 0xf0)== 0) && ((s2[0] & 0xf0) == 0)) 
1280                         return s1[0] - s2[0];
1281                 
1282                 return g_utf8_collate (s1, s2);
1283
1284         } else {
1285                 gint result;
1286                 gchar *n1, *n2;
1287
1288                 /* optimization: short cut iif first char is ascii */ 
1289                 if (((s1[0] & 0xf0) == 0) && ((s2[0] & 0xf0) == 0)) 
1290                         return tolower(s1[0]) - tolower(s2[0]);
1291                 
1292                 n1 = g_utf8_strdown (s1, -1);
1293                 n2 = g_utf8_strdown (s2, -1);
1294                 
1295                 result = g_utf8_collate (n1, n2);
1296                 
1297                 g_free (n1);
1298                 g_free (n2);
1299         
1300                 return result;
1301         }
1302 }
1303
1304
1305 const gchar*
1306 modest_text_utils_get_display_date (time_t date)
1307 {
1308 #define DATE_BUF_SIZE 64 
1309         static gchar date_buf[DATE_BUF_SIZE];
1310         
1311         /* calculate the # of days since epoch for 
1312          * for today and for the date provided 
1313          * based on idea from pvanhoof */
1314         int day      = time(NULL) / (24 * 60 * 60);
1315         int date_day = date       / (24 * 60 * 60);
1316
1317         /* if it's today, show the time, if it's not today, show the date instead */
1318
1319         if (day == date_day) /* is the date today? */
1320                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%X", date);
1321         else 
1322                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%x", date); 
1323
1324         return date_buf; /* this is a static buffer, don't free! */
1325 }
1326
1327
1328
1329 gboolean
1330 modest_text_utils_validate_folder_name (const gchar *folder_name)
1331 {
1332         /* based on http://msdn2.microsoft.com/en-us/library/aa365247.aspx,
1333          * with some extras */
1334         
1335         guint len;
1336         gint i;
1337         const gchar **cursor = NULL;
1338         const gchar *forbidden_names[] = { /* windows does not like these */
1339                 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6",
1340                 "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
1341                 ".", "..", "cur", "tmp", "new", NULL /* cur, tmp new  are reserved for Maildir */
1342         };
1343         
1344         /* cannot be NULL */
1345         if (!folder_name) 
1346                 return FALSE;
1347
1348         /* cannot be empty */
1349         len = strlen(folder_name);
1350         if (len == 0)
1351                 return FALSE;
1352         
1353         /* cannot start with a dot, vfat does not seem to like that */
1354         if (folder_name[0] == '.')
1355                 return FALSE;
1356
1357         /* cannot start or end with a space */
1358         if (g_ascii_isspace(folder_name[0]) || g_ascii_isspace(folder_name[len - 1]))
1359                 return FALSE; 
1360
1361         /* cannot contain a forbidden char */   
1362         for (i = 0; i < len; i++)
1363                 if (modest_text_utils_is_forbidden_char (folder_name[i], FOLDER_NAME_FORBIDDEN_CHARS))
1364                         return FALSE;
1365         
1366         /* cannot contain a forbidden word */
1367         if (len <= 4) {
1368                 for (cursor = forbidden_names; cursor && *cursor; ++cursor) {
1369                         if (g_ascii_strcasecmp (folder_name, *cursor) == 0)
1370                                 return FALSE;
1371                 }
1372         }
1373
1374         return TRUE; /* it's valid! */
1375 }
1376
1377
1378
1379 gboolean
1380 modest_text_utils_validate_domain_name (const gchar *domain)
1381 {
1382         gboolean valid = FALSE;
1383         regex_t rx;
1384         const gchar* domain_regex = "^([a-z0-9-]*[a-z0-9]\\.)+[a-z0-9-]*[a-z0-9]$";
1385
1386         g_return_val_if_fail (domain, FALSE);
1387         
1388         if (!domain)
1389                 return FALSE;
1390         
1391         memset (&rx, 0, sizeof(regex_t)); /* coverity wants this... */
1392                 
1393         /* domain name: all alphanum or '-' or '.',
1394          * but beginning/ending in alphanum */  
1395         if (regcomp (&rx, domain_regex, REG_ICASE|REG_EXTENDED|REG_NOSUB)) {
1396                 g_warning ("BUG: error in regexp");
1397                 return FALSE;
1398         }
1399         
1400         valid = (regexec (&rx, domain, 1, NULL, 0) == 0);
1401         regfree (&rx);
1402                 
1403         return valid;
1404 }
1405
1406
1407
1408 gboolean
1409 modest_text_utils_validate_email_address (const gchar *email_address,
1410                                           const gchar **invalid_char_position)
1411 {
1412         int count = 0;
1413         const gchar *c = NULL, *domain = NULL;
1414         static gchar *rfc822_specials = "()<>@,;:\\\"[]&";
1415         
1416         if (invalid_char_position)
1417                 *invalid_char_position = NULL;
1418         
1419         g_return_val_if_fail (email_address, FALSE);
1420         
1421         /* check that the email adress contains exactly one @ */
1422         if (!strstr(email_address, "@") || 
1423                         (strstr(email_address, "@") != g_strrstr(email_address, "@"))) 
1424                 return FALSE;
1425         
1426         /* first we validate the name portion (name@domain) */
1427         for (c = email_address;  *c;  c++) {
1428                 if (*c == '\"' && 
1429                     (c == email_address || 
1430                      *(c - 1) == '.' || 
1431                      *(c - 1) == '\"')) {
1432                         while (*++c) {
1433                                 if (*c == '\"') 
1434                                         break;
1435                                 if (*c == '\\' && (*++c == ' ')) 
1436                                         continue;
1437                                 if (*c <= ' ' || *c >= 127) 
1438                                         return FALSE;
1439                         }
1440                         if (!*c++) 
1441                                 return FALSE;
1442                         if (*c == '@') 
1443                                 break;
1444                         if (*c != '.') 
1445                                 return FALSE;
1446                         continue;
1447                 }
1448                 if (*c == '@') 
1449                         break;
1450                 if (*c <= ' ' || *c >= 127) 
1451                         return FALSE;
1452                 if (strchr(rfc822_specials, *c)) {
1453                         if (invalid_char_position)
1454                                 *invalid_char_position = c;
1455                         return FALSE;
1456                 }
1457         }
1458         if (c == email_address || *(c - 1) == '.') 
1459                 return FALSE;
1460
1461         /* next we validate the domain portion (name@domain) */
1462         if (!*(domain = ++c)) 
1463                 return FALSE;
1464         do {
1465                 if (*c == '.') {
1466                         if (c == domain || *(c - 1) == '.' || *(c + 1) == '\0') 
1467                                 return FALSE;
1468                         count++;
1469                 }
1470                 if (*c <= ' ' || *c >= 127) 
1471                         return FALSE;
1472                 if (strchr(rfc822_specials, *c)) {
1473                         if (invalid_char_position)
1474                                 *invalid_char_position = c;
1475                         return FALSE;
1476                 }
1477         } while (*++c);
1478
1479         return (count >= 1) ? TRUE : FALSE;
1480 }
1481
1482 gboolean 
1483 modest_text_utils_validate_recipient (const gchar *recipient, const gchar **invalid_char_position)
1484 {
1485         gchar *stripped, *current;
1486         gchar *right_part;
1487         gboolean has_error = FALSE;
1488
1489         if (invalid_char_position)
1490                 *invalid_char_position = NULL;
1491         
1492         g_return_val_if_fail (recipient, FALSE);
1493         
1494         if (modest_text_utils_validate_email_address (recipient, invalid_char_position))
1495                 return TRUE;
1496
1497         stripped = g_strdup (recipient);
1498         stripped = g_strstrip (stripped);
1499         current = stripped;
1500
1501         if (*current == '\0') {
1502                 g_free (stripped);
1503                 return FALSE;
1504         }
1505
1506         /* quoted string */
1507         if (*current == '\"') {
1508                 current = g_utf8_next_char (current);
1509                 has_error = TRUE;
1510                 for (; *current != '\0'; current = g_utf8_next_char (current)) {
1511                         if (*current == '\\') {
1512                                 /* TODO: This causes a warning, which breaks the build, 
1513                                  * because a gchar cannot be < 0.
1514                                  * murrayc. 
1515                                 if (current[1] <0) {
1516                                         has_error = TRUE;
1517                                         break;
1518                                 }
1519                                 */
1520                         } else if (*current == '\"') {
1521                                 has_error = FALSE;
1522                                 current = g_utf8_next_char (current);
1523                                 break;
1524                         }
1525                 }
1526         } else {
1527                 has_error = TRUE;
1528                 for (current = stripped ; *current != '\0'; current = g_utf8_next_char (current)) {
1529                         if (*current == '<') {
1530                                 has_error = FALSE;
1531                                 break;
1532                         }
1533                 }
1534         }
1535                 
1536         if (has_error) {
1537                 g_free (stripped);
1538                 return FALSE;
1539         }
1540
1541         right_part = g_strdup (current);
1542         g_free (stripped);
1543         right_part = g_strstrip (right_part);
1544
1545         if (g_str_has_prefix (right_part, "<") &&
1546             g_str_has_suffix (right_part, ">")) {
1547                 gchar *address;
1548                 gboolean valid;
1549
1550                 address = g_strndup (right_part+1, strlen (right_part) - 2);
1551                 g_free (right_part);
1552                 valid = modest_text_utils_validate_email_address (address, invalid_char_position);
1553                 g_free (address);
1554                 return valid;
1555         } else {
1556                 g_free (right_part);
1557                 return FALSE;
1558         }
1559 }
1560
1561
1562 gchar *
1563 modest_text_utils_get_display_size (guint64 size)
1564 {
1565         const guint KB=1024;
1566         const guint MB=1024 * KB;
1567         const guint GB=1024 * MB;
1568
1569         if (size == 0)
1570                 return g_strdup_printf(_FM("sfil_li_size_kb"), 0);
1571         if (0 < size && size < KB)
1572                 return g_strdup_printf (_FM("sfil_li_size_kb"), 1);
1573         else if (KB <= size && size < 100 * KB)
1574                 return g_strdup_printf (_FM("sfil_li_size_1kb_99kb"), size / KB);
1575         else if (100*KB <= size && size < MB)
1576                 return g_strdup_printf (_FM("sfil_li_size_100kb_1mb"), (float) size / MB);
1577         else if (MB <= size && size < 10*MB)
1578                 return g_strdup_printf (_FM("sfil_li_size_1mb_10mb"), (float) size / MB);
1579         else if (10*MB <= size && size < GB)
1580                 return g_strdup_printf (_FM("sfil_li_size_10mb_1gb"), size / MB);
1581         else
1582                 return g_strdup_printf (_FM("sfil_li_size_1gb_or_greater"), (float) size / GB); 
1583 }
1584
1585 static gchar *
1586 get_email_from_address (const gchar * address)
1587 {
1588         gchar *left_limit, *right_limit;
1589
1590         left_limit = strstr (address, "<");
1591         right_limit = g_strrstr (address, ">");
1592
1593         if ((left_limit == NULL)||(right_limit == NULL)|| (left_limit > right_limit))
1594                 return g_strdup (address);
1595         else
1596                 return g_strndup (left_limit + 1, (right_limit - left_limit) - 1);
1597 }
1598
1599 gchar *      
1600 modest_text_utils_get_color_string (GdkColor *color)
1601 {
1602         g_return_val_if_fail (color, NULL);
1603         
1604         return g_strdup_printf ("#%x%x%x%x%x%x%x%x%x%x%x%x",
1605                                 (color->red >> 12)   & 0xf, (color->red >> 8)   & 0xf,
1606                                 (color->red >>  4)   & 0xf, (color->red)        & 0xf,
1607                                 (color->green >> 12) & 0xf, (color->green >> 8) & 0xf,
1608                                 (color->green >>  4) & 0xf, (color->green)      & 0xf,
1609                                 (color->blue >> 12)  & 0xf, (color->blue >> 8)  & 0xf,
1610                                 (color->blue >>  4)  & 0xf, (color->blue)       & 0xf);
1611 }
1612
1613 gchar *
1614 modest_text_utils_text_buffer_get_text (GtkTextBuffer *buffer)
1615 {
1616         GtkTextIter start, end;
1617         gchar *slice, *current;
1618         GString *result = g_string_new ("");
1619
1620         g_return_val_if_fail (buffer && GTK_IS_TEXT_BUFFER (buffer), NULL);
1621         
1622         gtk_text_buffer_get_start_iter (buffer, &start);
1623         gtk_text_buffer_get_end_iter (buffer, &end);
1624
1625         slice = gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
1626         current = slice;
1627
1628         while (current && current != '\0') {
1629                 if (g_utf8_get_char (current) == 0xFFFC) {
1630                         result = g_string_append_c (result, ' ');
1631                         current = g_utf8_next_char (current);
1632                 } else {
1633                         gchar *next = g_utf8_strchr (current, -1, 0xFFFC);
1634                         if (next == NULL) {
1635                                 result = g_string_append (result, current);
1636                         } else {
1637                                 result = g_string_append_len (result, current, next - current);
1638                         }
1639                         current = next;
1640                 }
1641         }
1642         g_free (slice);
1643
1644         return g_string_free (result, FALSE);
1645         
1646 }
1647
1648 gboolean
1649 modest_text_utils_is_forbidden_char (const gchar character,
1650                                      ModestTextUtilsForbiddenCharType type)
1651 {
1652         gint i, len;
1653         const gchar *forbidden_chars = NULL;
1654         
1655         /* We need to get the length in the switch because the
1656            compiler needs to know the size at compile time */
1657         switch (type) {
1658         case ACCOUNT_TITLE_FORBIDDEN_CHARS:
1659                 forbidden_chars = account_title_forbidden_chars;
1660                 len = G_N_ELEMENTS (account_title_forbidden_chars);
1661                 break;
1662         case FOLDER_NAME_FORBIDDEN_CHARS:
1663                 forbidden_chars = folder_name_forbidden_chars;
1664                 len = G_N_ELEMENTS (folder_name_forbidden_chars);
1665                 break;
1666         case USER_NAME_FORBIDDEN_NAMES:
1667                 forbidden_chars = user_name_forbidden_chars;
1668                 len = G_N_ELEMENTS (user_name_forbidden_chars);
1669                 break;
1670         default:
1671                 g_return_val_if_reached (TRUE);
1672         }
1673
1674         for (i = 0; i < len ; i++)
1675                 if (forbidden_chars[i] == character)
1676                         return TRUE;
1677
1678         return FALSE; /* it's valid! */
1679 }
1680
1681 gchar *      
1682 modest_text_utils_label_get_selection (GtkLabel *label)
1683 {
1684         gint start, end;
1685         gchar *selection;
1686
1687         if (gtk_label_get_selection_bounds (GTK_LABEL (label), &start, &end)) {
1688                 const gchar *start_offset;
1689                 const gchar *end_offset;
1690                 start_offset = gtk_label_get_text (GTK_LABEL (label));
1691                 start_offset = g_utf8_offset_to_pointer (start_offset, start);
1692                 end_offset = gtk_label_get_text (GTK_LABEL (label));
1693                 end_offset = g_utf8_offset_to_pointer (end_offset, end);
1694                 selection = g_strndup (start_offset, end_offset - start_offset);
1695                 return selection;
1696         } else {
1697                 return g_strdup ("");
1698         }
1699 }
1700
1701 static gboolean
1702 _forward_search_image_char (gunichar ch,
1703                             gpointer userdata)
1704 {
1705         return (ch == 0xFFFC);
1706 }
1707
1708 gboolean
1709 modest_text_utils_buffer_selection_is_valid (GtkTextBuffer *buffer)
1710 {
1711         gboolean result;
1712         GtkTextIter start, end;
1713
1714         g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
1715
1716         result = gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (buffer));
1717
1718         /* check there are no images in selection */
1719         if (result) {
1720                 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
1721                 if (gtk_text_iter_get_char (&start)== 0xFFFC)
1722                         result = FALSE;
1723                 else {
1724                         gtk_text_iter_backward_char (&end);
1725                         if (gtk_text_iter_forward_find_char (&start, _forward_search_image_char,
1726                                                              NULL, &end))
1727                                 result = FALSE;
1728                 }
1729                                     
1730         }
1731
1732         return result;
1733 }
1734
1735 gchar *
1736 modest_text_utils_escape_mnemonics (const gchar *text)
1737 {
1738         const gchar *p;
1739         GString *result = NULL;
1740
1741         if (text == NULL)
1742                 return NULL;
1743
1744         result = g_string_new ("");
1745         for (p = text; *p != '\0'; p++) {
1746                 if (*p == '_')
1747                         result = g_string_append (result, "__");
1748                 else
1749                         result = g_string_append_c (result, *p);
1750         }
1751         
1752         return g_string_free (result, FALSE);
1753 }