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