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