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