Added bug 90185 to changelog
[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 #define SIGNATURE_MARKER "--"
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 (SIGNATURE_MARKER,"\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, SIGNATURE_MARKER, "\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 void
933 quote_html_add_to_gstring (GString *string,
934                            const gchar *text)
935 {
936         if (text && strcmp (text, "")) {
937                 gchar *html_text = modest_text_utils_convert_to_html_body (text, -1, TRUE);
938                 g_string_append_printf (string, "%s<br/>", html_text);
939                 g_free (html_text);
940         }
941 }
942
943 static gchar*
944 modest_text_utils_quote_html (const gchar *text, 
945                               const gchar *cite, 
946                               const gchar *signature,
947                               GList *attachments,
948                               int limit)
949 {
950         GString *result_string;
951
952         result_string = 
953                 g_string_new ( \
954                               "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" \
955                               "<html>\n"                                \
956                               "<body>\n");
957
958         if (text || cite || signature) {
959                 g_string_append (result_string, "<pre>");
960                 quote_html_add_to_gstring (result_string, cite);
961                 quote_html_add_to_gstring (result_string, text);
962                 if (attachments) {
963                         gchar *attachments_string = quoted_attachments (attachments);
964                         quote_html_add_to_gstring (result_string, attachments_string);
965                         g_free (attachments_string);
966                 }
967                 if (signature) {
968                         quote_html_add_to_gstring (result_string, SIGNATURE_MARKER);
969                         quote_html_add_to_gstring (result_string, signature);
970                 }
971                 g_string_append (result_string, "</pre>");
972         }
973         g_string_append (result_string, "</body>");
974         g_string_append (result_string, "</html>");
975
976         return g_string_free (result_string, FALSE);
977 }
978
979 static gint 
980 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
981 {
982         return match2->offset - match1->offset;
983 }
984
985 static gboolean url_matches_block = 0;
986 static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
987
988
989 static gboolean
990 compile_patterns ()
991 {
992         guint i;
993         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
994         for (i = 0; i != pattern_num; ++i) {
995                 patterns[i].preg = g_slice_new0 (regex_t);
996                 
997                 /* this should not happen */
998                 if (regcomp (patterns[i].preg, patterns[i].regex,
999                              REG_ICASE|REG_EXTENDED|REG_NEWLINE) != 0) {
1000                         g_warning ("%s: error in regexp:\n%s\n", __FUNCTION__, patterns[i].regex);
1001                         return FALSE;
1002                 }
1003         }
1004         return TRUE;
1005 }
1006
1007 static void 
1008 free_patterns ()
1009 {
1010         guint i;
1011         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
1012         for (i = 0; i != pattern_num; ++i) {
1013                 regfree (patterns[i].preg);
1014                 g_slice_free  (regex_t, patterns[i].preg);
1015         } /* don't free patterns itself -- it's static */
1016 }
1017
1018 void
1019 modest_text_utils_hyperlinkify_begin (void)
1020 {
1021         if (url_matches_block == 0)
1022                 compile_patterns ();
1023         url_matches_block ++;
1024 }
1025
1026 void
1027 modest_text_utils_hyperlinkify_end (void)
1028 {
1029         url_matches_block--;
1030         if (url_matches_block <= 0)
1031                 free_patterns ();
1032 }
1033
1034
1035 static GSList*
1036 get_url_matches (GString *txt, gint offset)
1037 {
1038         regmatch_t rm;
1039         guint rv, i, tmp_offset = 0;
1040         GSList *match_list = NULL;
1041
1042         const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
1043
1044         /* initalize the regexps */
1045         modest_text_utils_hyperlinkify_begin ();
1046
1047         /* find all the matches */
1048         for (i = 0; i != pattern_num; ++i) {
1049                 tmp_offset     = offset;        
1050                 while (1) {
1051                         url_match_t *match;
1052                         gboolean is_submatch;
1053                         GSList *cursor;
1054                         
1055                         if ((rv = regexec (patterns[i].preg, txt->str + tmp_offset, 1, &rm, 0)) != 0) {
1056                                 g_return_val_if_fail (rv == REG_NOMATCH, NULL); /* this should not happen */
1057                                 break; /* try next regexp */ 
1058                         }
1059                         if (rm.rm_so == -1)
1060                                 break;
1061                         
1062                         is_submatch = FALSE;
1063                         /* check  old matches to see if this has already been matched */
1064                         cursor = match_list;
1065                         while (cursor && !is_submatch) {
1066                                 const url_match_t *old_match =
1067                                         (const url_match_t *) cursor->data;
1068                                 guint new_offset = tmp_offset + rm.rm_so;
1069                                 is_submatch = (new_offset >  old_match->offset &&
1070                                                new_offset <  old_match->offset + old_match->len);
1071                                 cursor = g_slist_next (cursor);
1072                         }
1073
1074                         if (!is_submatch) {
1075                                 /* make a list of our matches (<offset, len, prefix> tupels)*/
1076                                 match = g_slice_new (url_match_t);
1077                                 match->offset = tmp_offset + rm.rm_so;
1078                                 match->len    = rm.rm_eo - rm.rm_so;
1079                                 match->prefix = patterns[i].prefix;
1080                                 match_list = g_slist_prepend (match_list, match);
1081                         }               
1082                         tmp_offset += rm.rm_eo;
1083                 }
1084         }
1085
1086         modest_text_utils_hyperlinkify_end ();
1087         
1088         /* now sort the list, so the matches are in reverse order of occurence.
1089          * that way, we can do the replacements starting from the end, so we don't need
1090          * to recalculate the offsets
1091          */
1092         match_list = g_slist_sort (match_list,
1093                                    (GCompareFunc)cmp_offsets_reverse); 
1094         return match_list;      
1095 }
1096
1097
1098
1099 /* replace all occurences of needle in haystack with repl*/
1100 static gchar*
1101 replace_string (const gchar *haystack, const gchar *needle, gchar repl)
1102 {
1103         gchar *str, *cursor;
1104
1105         if (!haystack || !needle || strlen(needle) == 0)
1106                 return haystack ? g_strdup(haystack) : NULL;
1107         
1108         str = g_strdup (haystack);
1109
1110         for (cursor = str; cursor && *cursor; ++cursor) {
1111                 if (g_str_has_prefix (cursor, needle)) {
1112                         cursor[0] = repl;
1113                         memmove (cursor + 1,
1114                                  cursor + strlen (needle),
1115                                  strlen (cursor + strlen (needle)) + 1);
1116                 }
1117         }
1118         
1119         return str;
1120 }
1121
1122 static void
1123 hyperlinkify_plain_text (GString *txt, gint offset)
1124 {
1125         GSList *cursor;
1126         GSList *match_list = get_url_matches (txt, offset);
1127
1128         /* we will work backwards, so the offsets stay valid */
1129         for (cursor = match_list; cursor; cursor = cursor->next) {
1130
1131                 url_match_t *match = (url_match_t*) cursor->data;
1132                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
1133                 gchar *repl = NULL; /* replacement  */
1134
1135                 /* the string still contains $(MARK_AMP_URI_STR)"amp;" for each
1136                  * '&' in the original, because of the text->html conversion.
1137                  * in the href-URL (and only there), we must convert that back to
1138                  * '&'
1139                  */
1140                 gchar *href_url = replace_string (url, MARK_AMP_URI_STR "amp;", '&');
1141                 
1142                 /* the prefix is NULL: use the one that is already there */
1143                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
1144                                         match->prefix ? match->prefix : EMPTY_STRING, 
1145                                         href_url, url);
1146
1147                 /* replace the old thing with our hyperlink
1148                  * replacement thing */
1149                 g_string_erase  (txt, match->offset, match->len);
1150                 g_string_insert (txt, match->offset, repl);
1151                 
1152                 g_free (url);
1153                 g_free (repl);
1154                 g_free (href_url);
1155
1156                 g_slice_free (url_match_t, match);      
1157         }
1158         
1159         g_slist_free (match_list);
1160 }
1161
1162 void
1163 modest_text_utils_hyperlinkify (GString *string_buffer)
1164 {
1165         gchar *after_body;
1166         gint offset = 0;
1167
1168         after_body = strstr (string_buffer->str, "<body>");
1169         if (after_body != NULL)
1170                 offset = after_body - string_buffer->str;
1171         hyperlinkify_plain_text (string_buffer, offset);
1172 }
1173
1174
1175 /* for optimization reasons, we change the string in-place */
1176 void
1177 modest_text_utils_get_display_address (gchar *address)
1178 {
1179         int i;
1180
1181         g_return_if_fail (address);
1182         
1183         if (!address)
1184                 return;
1185         
1186         /* should not be needed, and otherwise, we probably won't screw up the address
1187          * more than it already is :) 
1188          * g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL);
1189          * */
1190         
1191         /* remove leading whitespace */
1192         if (address[0] == ' ')
1193                 g_strchug (address);
1194                 
1195         for (i = 0; address[i]; ++i) {
1196                 if (address[i] == '<') {
1197                         if (G_UNLIKELY(i == 0))
1198                                 return; /* there's nothing else, leave it */
1199                         else {
1200                                 address[i] = '\0'; /* terminate the string here */
1201                                 return;
1202                         }
1203                 }
1204         }
1205 }
1206
1207
1208
1209
1210
1211 gchar *
1212 modest_text_utils_get_email_address (const gchar *full_address)
1213 {
1214         const gchar *left, *right;
1215
1216         g_return_val_if_fail (full_address, NULL);
1217         
1218         if (!full_address)
1219                 return NULL;
1220         
1221         g_return_val_if_fail (g_utf8_validate (full_address, -1, NULL), NULL);
1222         
1223         left = g_strrstr_len (full_address, strlen(full_address), "<");
1224         if (left == NULL)
1225                 return g_strdup (full_address);
1226
1227         right = g_strstr_len (left, strlen(left), ">");
1228         if (right == NULL)
1229                 return g_strdup (full_address);
1230
1231         return g_strndup (left + 1, right - left - 1);
1232 }
1233
1234 gint 
1235 modest_text_utils_get_subject_prefix_len (const gchar *sub)
1236 {
1237         gint prefix_len = 0;    
1238
1239         g_return_val_if_fail (sub, 0);
1240
1241         if (!sub)
1242                 return 0;
1243         
1244         /* optimization: "Re", "RE", "re","Fwd", "FWD", "fwd","FW","Fw", "fw" */
1245         if (sub[0] != 'R' && sub[0] != 'F' && sub[0] != 'r' && sub[0] != 'f')
1246                 return 0;
1247         else if (sub[0] && sub[1] != 'e' && sub[1] != 'E' && sub[1] != 'w' && sub[1] != 'W')
1248                 return 0;
1249
1250         prefix_len = 2;
1251         if (sub[2] == 'd')
1252                 ++prefix_len;
1253
1254         /* skip over a [...] block */
1255         if (sub[prefix_len] == '[') {
1256                 int c = prefix_len + 1;
1257                 while (sub[c] && sub[c] != ']')
1258                         ++c;
1259                 if (sub[c])
1260                         return 0; /* no end to the ']' found */
1261                 else
1262                         prefix_len = c + 1;
1263         }
1264
1265         /* did we find the ':' ? */
1266         if (sub[prefix_len] == ':') {
1267                 ++prefix_len;
1268                 if (sub[prefix_len] == ' ')
1269                         ++prefix_len;
1270                 prefix_len += modest_text_utils_get_subject_prefix_len (sub + prefix_len);
1271 /*              g_warning ("['%s','%s']", sub, (char*) sub + prefix_len); */
1272                 return prefix_len;
1273         } else
1274                 return 0;
1275 }
1276
1277
1278 gint
1279 modest_text_utils_utf8_strcmp (const gchar* s1, const gchar *s2, gboolean insensitive)
1280 {
1281
1282 /* work even when s1 and/or s2 == NULL */
1283         if (G_UNLIKELY(s1 == s2))
1284                 return 0;
1285         if (G_UNLIKELY(!s1))
1286                 return -1;
1287         if (G_UNLIKELY(!s2))
1288                 return 1;
1289         
1290         /* if it's not case sensitive */
1291         if (!insensitive) {
1292
1293                 /* optimization: shortcut if first char is ascii */ 
1294                 if (((s1[0] & 0xf0)== 0) && ((s2[0] & 0xf0) == 0)) 
1295                         return s1[0] - s2[0];
1296                 
1297                 return g_utf8_collate (s1, s2);
1298
1299         } else {
1300                 gint result;
1301                 gchar *n1, *n2;
1302
1303                 /* optimization: short cut iif first char is ascii */ 
1304                 if (((s1[0] & 0xf0) == 0) && ((s2[0] & 0xf0) == 0)) 
1305                         return tolower(s1[0]) - tolower(s2[0]);
1306                 
1307                 n1 = g_utf8_strdown (s1, -1);
1308                 n2 = g_utf8_strdown (s2, -1);
1309                 
1310                 result = g_utf8_collate (n1, n2);
1311                 
1312                 g_free (n1);
1313                 g_free (n2);
1314         
1315                 return result;
1316         }
1317 }
1318
1319
1320 const gchar*
1321 modest_text_utils_get_display_date (time_t date)
1322 {
1323 #define DATE_BUF_SIZE 64 
1324         static gchar date_buf[DATE_BUF_SIZE];
1325         
1326         /* calculate the # of days since epoch for 
1327          * for today and for the date provided 
1328          * based on idea from pvanhoof */
1329         int day      = time(NULL) / (24 * 60 * 60);
1330         int date_day = date       / (24 * 60 * 60);
1331
1332         /* if it's today, show the time, if it's not today, show the date instead */
1333
1334         if (day == date_day) /* is the date today? */
1335                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%X", date);
1336         else 
1337                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%x", date); 
1338
1339         return date_buf; /* this is a static buffer, don't free! */
1340 }
1341
1342
1343
1344 gboolean
1345 modest_text_utils_validate_folder_name (const gchar *folder_name)
1346 {
1347         /* based on http://msdn2.microsoft.com/en-us/library/aa365247.aspx,
1348          * with some extras */
1349         
1350         guint len;
1351         gint i;
1352         const gchar **cursor = NULL;
1353         const gchar *forbidden_names[] = { /* windows does not like these */
1354                 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6",
1355                 "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
1356                 ".", "..", "cur", "tmp", "new", NULL /* cur, tmp new  are reserved for Maildir */
1357         };
1358         
1359         /* cannot be NULL */
1360         if (!folder_name) 
1361                 return FALSE;
1362
1363         /* cannot be empty */
1364         len = strlen(folder_name);
1365         if (len == 0)
1366                 return FALSE;
1367         
1368         /* cannot start with a dot, vfat does not seem to like that */
1369         if (folder_name[0] == '.')
1370                 return FALSE;
1371
1372         /* cannot start or end with a space */
1373         if (g_ascii_isspace(folder_name[0]) || g_ascii_isspace(folder_name[len - 1]))
1374                 return FALSE; 
1375
1376         /* cannot contain a forbidden char */   
1377         for (i = 0; i < len; i++)
1378                 if (modest_text_utils_is_forbidden_char (folder_name[i], FOLDER_NAME_FORBIDDEN_CHARS))
1379                         return FALSE;
1380         
1381         /* cannot contain a forbidden word */
1382         if (len <= 4) {
1383                 for (cursor = forbidden_names; cursor && *cursor; ++cursor) {
1384                         if (g_ascii_strcasecmp (folder_name, *cursor) == 0)
1385                                 return FALSE;
1386                 }
1387         }
1388
1389         return TRUE; /* it's valid! */
1390 }
1391
1392
1393
1394 gboolean
1395 modest_text_utils_validate_domain_name (const gchar *domain)
1396 {
1397         gboolean valid = FALSE;
1398         regex_t rx;
1399         const gchar* domain_regex = "^([a-z0-9-]*[a-z0-9]\\.)+[a-z0-9-]*[a-z0-9]$";
1400
1401         g_return_val_if_fail (domain, FALSE);
1402         
1403         if (!domain)
1404                 return FALSE;
1405         
1406         memset (&rx, 0, sizeof(regex_t)); /* coverity wants this... */
1407                 
1408         /* domain name: all alphanum or '-' or '.',
1409          * but beginning/ending in alphanum */  
1410         if (regcomp (&rx, domain_regex, REG_ICASE|REG_EXTENDED|REG_NOSUB)) {
1411                 g_warning ("BUG: error in regexp");
1412                 return FALSE;
1413         }
1414         
1415         valid = (regexec (&rx, domain, 1, NULL, 0) == 0);
1416         regfree (&rx);
1417                 
1418         return valid;
1419 }
1420
1421
1422
1423 gboolean
1424 modest_text_utils_validate_email_address (const gchar *email_address,
1425                                           const gchar **invalid_char_position)
1426 {
1427         int count = 0;
1428         const gchar *c = NULL, *domain = NULL;
1429         static gchar *rfc822_specials = "()<>@,;:\\\"[]&";
1430         
1431         if (invalid_char_position)
1432                 *invalid_char_position = NULL;
1433         
1434         g_return_val_if_fail (email_address, FALSE);
1435         
1436         /* check that the email adress contains exactly one @ */
1437         if (!strstr(email_address, "@") || 
1438                         (strstr(email_address, "@") != g_strrstr(email_address, "@"))) 
1439                 return FALSE;
1440         
1441         /* first we validate the name portion (name@domain) */
1442         for (c = email_address;  *c;  c++) {
1443                 if (*c == '\"' && 
1444                     (c == email_address || 
1445                      *(c - 1) == '.' || 
1446                      *(c - 1) == '\"')) {
1447                         while (*++c) {
1448                                 if (*c == '\"') 
1449                                         break;
1450                                 if (*c == '\\' && (*++c == ' ')) 
1451                                         continue;
1452                                 if (*c <= ' ' || *c >= 127) 
1453                                         return FALSE;
1454                         }
1455                         if (!*c++) 
1456                                 return FALSE;
1457                         if (*c == '@') 
1458                                 break;
1459                         if (*c != '.') 
1460                                 return FALSE;
1461                         continue;
1462                 }
1463                 if (*c == '@') 
1464                         break;
1465                 if (*c <= ' ' || *c >= 127) 
1466                         return FALSE;
1467                 if (strchr(rfc822_specials, *c)) {
1468                         if (invalid_char_position)
1469                                 *invalid_char_position = c;
1470                         return FALSE;
1471                 }
1472         }
1473         if (c == email_address || *(c - 1) == '.') 
1474                 return FALSE;
1475
1476         /* next we validate the domain portion (name@domain) */
1477         if (!*(domain = ++c)) 
1478                 return FALSE;
1479         do {
1480                 if (*c == '.') {
1481                         if (c == domain || *(c - 1) == '.' || *(c + 1) == '\0') 
1482                                 return FALSE;
1483                         count++;
1484                 }
1485                 if (*c <= ' ' || *c >= 127) 
1486                         return FALSE;
1487                 if (strchr(rfc822_specials, *c)) {
1488                         if (invalid_char_position)
1489                                 *invalid_char_position = c;
1490                         return FALSE;
1491                 }
1492         } while (*++c);
1493
1494         return (count >= 1) ? TRUE : FALSE;
1495 }
1496
1497 gboolean 
1498 modest_text_utils_validate_recipient (const gchar *recipient, const gchar **invalid_char_position)
1499 {
1500         gchar *stripped, *current;
1501         gchar *right_part;
1502         gboolean has_error = FALSE;
1503
1504         if (invalid_char_position)
1505                 *invalid_char_position = NULL;
1506         
1507         g_return_val_if_fail (recipient, FALSE);
1508         
1509         if (modest_text_utils_validate_email_address (recipient, invalid_char_position))
1510                 return TRUE;
1511
1512         stripped = g_strdup (recipient);
1513         stripped = g_strstrip (stripped);
1514         current = stripped;
1515
1516         if (*current == '\0') {
1517                 g_free (stripped);
1518                 return FALSE;
1519         }
1520
1521         /* quoted string */
1522         if (*current == '\"') {
1523                 current = g_utf8_next_char (current);
1524                 has_error = TRUE;
1525                 for (; *current != '\0'; current = g_utf8_next_char (current)) {
1526                         if (*current == '\\') {
1527                                 /* TODO: This causes a warning, which breaks the build, 
1528                                  * because a gchar cannot be < 0.
1529                                  * murrayc. 
1530                                 if (current[1] <0) {
1531                                         has_error = TRUE;
1532                                         break;
1533                                 }
1534                                 */
1535                         } else if (*current == '\"') {
1536                                 has_error = FALSE;
1537                                 current = g_utf8_next_char (current);
1538                                 break;
1539                         }
1540                 }
1541         } else {
1542                 has_error = TRUE;
1543                 for (current = stripped ; *current != '\0'; current = g_utf8_next_char (current)) {
1544                         if (*current == '<') {
1545                                 has_error = FALSE;
1546                                 break;
1547                         }
1548                 }
1549         }
1550                 
1551         if (has_error) {
1552                 g_free (stripped);
1553                 return FALSE;
1554         }
1555
1556         right_part = g_strdup (current);
1557         g_free (stripped);
1558         right_part = g_strstrip (right_part);
1559
1560         if (g_str_has_prefix (right_part, "<") &&
1561             g_str_has_suffix (right_part, ">")) {
1562                 gchar *address;
1563                 gboolean valid;
1564
1565                 address = g_strndup (right_part+1, strlen (right_part) - 2);
1566                 g_free (right_part);
1567                 valid = modest_text_utils_validate_email_address (address, invalid_char_position);
1568                 g_free (address);
1569                 return valid;
1570         } else {
1571                 g_free (right_part);
1572                 return FALSE;
1573         }
1574 }
1575
1576
1577 gchar *
1578 modest_text_utils_get_display_size (guint64 size)
1579 {
1580         const guint KB=1024;
1581         const guint MB=1024 * KB;
1582         const guint GB=1024 * MB;
1583
1584         if (size == 0)
1585                 return g_strdup_printf(_FM("sfil_li_size_kb"), 0);
1586         if (0 < size && size < KB)
1587                 return g_strdup_printf (_FM("sfil_li_size_kb"), 1);
1588         else if (KB <= size && size < 100 * KB)
1589                 return g_strdup_printf (_FM("sfil_li_size_1kb_99kb"), size / KB);
1590         else if (100*KB <= size && size < MB)
1591                 return g_strdup_printf (_FM("sfil_li_size_100kb_1mb"), (float) size / MB);
1592         else if (MB <= size && size < 10*MB)
1593                 return g_strdup_printf (_FM("sfil_li_size_1mb_10mb"), (float) size / MB);
1594         else if (10*MB <= size && size < GB)
1595                 return g_strdup_printf (_FM("sfil_li_size_10mb_1gb"), size / MB);
1596         else
1597                 return g_strdup_printf (_FM("sfil_li_size_1gb_or_greater"), (float) size / GB); 
1598 }
1599
1600 static gchar *
1601 get_email_from_address (const gchar * address)
1602 {
1603         gchar *left_limit, *right_limit;
1604
1605         left_limit = strstr (address, "<");
1606         right_limit = g_strrstr (address, ">");
1607
1608         if ((left_limit == NULL)||(right_limit == NULL)|| (left_limit > right_limit))
1609                 return g_strdup (address);
1610         else
1611                 return g_strndup (left_limit + 1, (right_limit - left_limit) - 1);
1612 }
1613
1614 gchar *      
1615 modest_text_utils_get_color_string (GdkColor *color)
1616 {
1617         g_return_val_if_fail (color, NULL);
1618         
1619         return g_strdup_printf ("#%x%x%x%x%x%x%x%x%x%x%x%x",
1620                                 (color->red >> 12)   & 0xf, (color->red >> 8)   & 0xf,
1621                                 (color->red >>  4)   & 0xf, (color->red)        & 0xf,
1622                                 (color->green >> 12) & 0xf, (color->green >> 8) & 0xf,
1623                                 (color->green >>  4) & 0xf, (color->green)      & 0xf,
1624                                 (color->blue >> 12)  & 0xf, (color->blue >> 8)  & 0xf,
1625                                 (color->blue >>  4)  & 0xf, (color->blue)       & 0xf);
1626 }
1627
1628 gchar *
1629 modest_text_utils_text_buffer_get_text (GtkTextBuffer *buffer)
1630 {
1631         GtkTextIter start, end;
1632         gchar *slice, *current;
1633         GString *result = g_string_new ("");
1634
1635         g_return_val_if_fail (buffer && GTK_IS_TEXT_BUFFER (buffer), NULL);
1636         
1637         gtk_text_buffer_get_start_iter (buffer, &start);
1638         gtk_text_buffer_get_end_iter (buffer, &end);
1639
1640         slice = gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
1641         current = slice;
1642
1643         while (current && current != '\0') {
1644                 if (g_utf8_get_char (current) == 0xFFFC) {
1645                         result = g_string_append_c (result, ' ');
1646                         current = g_utf8_next_char (current);
1647                 } else {
1648                         gchar *next = g_utf8_strchr (current, -1, 0xFFFC);
1649                         if (next == NULL) {
1650                                 result = g_string_append (result, current);
1651                         } else {
1652                                 result = g_string_append_len (result, current, next - current);
1653                         }
1654                         current = next;
1655                 }
1656         }
1657         g_free (slice);
1658
1659         return g_string_free (result, FALSE);
1660         
1661 }
1662
1663 gboolean
1664 modest_text_utils_is_forbidden_char (const gchar character,
1665                                      ModestTextUtilsForbiddenCharType type)
1666 {
1667         gint i, len;
1668         const gchar *forbidden_chars = NULL;
1669         
1670         /* We need to get the length in the switch because the
1671            compiler needs to know the size at compile time */
1672         switch (type) {
1673         case ACCOUNT_TITLE_FORBIDDEN_CHARS:
1674                 forbidden_chars = account_title_forbidden_chars;
1675                 len = G_N_ELEMENTS (account_title_forbidden_chars);
1676                 break;
1677         case FOLDER_NAME_FORBIDDEN_CHARS:
1678                 forbidden_chars = folder_name_forbidden_chars;
1679                 len = G_N_ELEMENTS (folder_name_forbidden_chars);
1680                 break;
1681         case USER_NAME_FORBIDDEN_NAMES:
1682                 forbidden_chars = user_name_forbidden_chars;
1683                 len = G_N_ELEMENTS (user_name_forbidden_chars);
1684                 break;
1685         default:
1686                 g_return_val_if_reached (TRUE);
1687         }
1688
1689         for (i = 0; i < len ; i++)
1690                 if (forbidden_chars[i] == character)
1691                         return TRUE;
1692
1693         return FALSE; /* it's valid! */
1694 }
1695
1696 gchar *      
1697 modest_text_utils_label_get_selection (GtkLabel *label)
1698 {
1699         gint start, end;
1700         gchar *selection;
1701
1702         if (gtk_label_get_selection_bounds (GTK_LABEL (label), &start, &end)) {
1703                 const gchar *start_offset;
1704                 const gchar *end_offset;
1705                 start_offset = gtk_label_get_text (GTK_LABEL (label));
1706                 start_offset = g_utf8_offset_to_pointer (start_offset, start);
1707                 end_offset = gtk_label_get_text (GTK_LABEL (label));
1708                 end_offset = g_utf8_offset_to_pointer (end_offset, end);
1709                 selection = g_strndup (start_offset, end_offset - start_offset);
1710                 return selection;
1711         } else {
1712                 return g_strdup ("");
1713         }
1714 }
1715
1716 static gboolean
1717 _forward_search_image_char (gunichar ch,
1718                             gpointer userdata)
1719 {
1720         return (ch == 0xFFFC);
1721 }
1722
1723 gboolean
1724 modest_text_utils_buffer_selection_is_valid (GtkTextBuffer *buffer)
1725 {
1726         gboolean result;
1727         GtkTextIter start, end;
1728
1729         g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
1730
1731         result = gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (buffer));
1732
1733         /* check there are no images in selection */
1734         if (result) {
1735                 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
1736                 if (gtk_text_iter_get_char (&start)== 0xFFFC)
1737                         result = FALSE;
1738                 else {
1739                         gtk_text_iter_backward_char (&end);
1740                         if (gtk_text_iter_forward_find_char (&start, _forward_search_image_char,
1741                                                              NULL, &end))
1742                                 result = FALSE;
1743                 }
1744                                     
1745         }
1746
1747         return result;
1748 }
1749
1750 gchar *
1751 modest_text_utils_escape_mnemonics (const gchar *text)
1752 {
1753         const gchar *p;
1754         GString *result = NULL;
1755
1756         if (text == NULL)
1757                 return NULL;
1758
1759         result = g_string_new ("");
1760         for (p = text; *p != '\0'; p++) {
1761                 if (*p == '_')
1762                         result = g_string_append (result, "__");
1763                 else
1764                         result = g_string_append_c (result, *p);
1765         }
1766         
1767         return g_string_free (result, FALSE);
1768 }