0b8927eaf0364bc9f48fd8698fec46db14f4dc0e
[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;
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);
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);
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)
1018 {
1019         regmatch_t rm;
1020         guint rv, i, 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                 offset     = 0; 
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 + 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 = 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 = 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                         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)
1105 {
1106         GSList *cursor;
1107         GSList *match_list = get_url_matches (txt);
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
1144 /* for optimization reasons, we change the string in-place */
1145 void
1146 modest_text_utils_get_display_address (gchar *address)
1147 {
1148         int i;
1149
1150         g_return_if_fail (address);
1151         
1152         if (!address)
1153                 return;
1154         
1155         /* should not be needed, and otherwise, we probably won't screw up the address
1156          * more than it already is :) 
1157          * g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL);
1158          * */
1159         
1160         /* remove leading whitespace */
1161         if (address[0] == ' ')
1162                 g_strchug (address);
1163                 
1164         for (i = 0; address[i]; ++i) {
1165                 if (address[i] == '<') {
1166                         if (G_UNLIKELY(i == 0))
1167                                 return; /* there's nothing else, leave it */
1168                         else {
1169                                 address[i] = '\0'; /* terminate the string here */
1170                                 return;
1171                         }
1172                 }
1173         }
1174 }
1175
1176
1177
1178
1179
1180 gchar *
1181 modest_text_utils_get_email_address (const gchar *full_address)
1182 {
1183         const gchar *left, *right;
1184
1185         g_return_val_if_fail (full_address, NULL);
1186         
1187         if (!full_address)
1188                 return NULL;
1189         
1190         g_return_val_if_fail (g_utf8_validate (full_address, -1, NULL), NULL);
1191         
1192         left = g_strrstr_len (full_address, strlen(full_address), "<");
1193         if (left == NULL)
1194                 return g_strdup (full_address);
1195
1196         right = g_strstr_len (left, strlen(left), ">");
1197         if (right == NULL)
1198                 return g_strdup (full_address);
1199
1200         return g_strndup (left + 1, right - left - 1);
1201 }
1202
1203 gint 
1204 modest_text_utils_get_subject_prefix_len (const gchar *sub)
1205 {
1206         gint prefix_len = 0;    
1207
1208         g_return_val_if_fail (sub, 0);
1209
1210         if (!sub)
1211                 return 0;
1212         
1213         /* optimization: "Re", "RE", "re","Fwd", "FWD", "fwd","FW","Fw", "fw" */
1214         if (sub[0] != 'R' && sub[0] != 'F' && sub[0] != 'r' && sub[0] != 'f')
1215                 return 0;
1216         else if (sub[0] && sub[1] != 'e' && sub[1] != 'E' && sub[1] != 'w' && sub[1] != 'W')
1217                 return 0;
1218
1219         prefix_len = 2;
1220         if (sub[2] == 'd')
1221                 ++prefix_len;
1222
1223         /* skip over a [...] block */
1224         if (sub[prefix_len] == '[') {
1225                 int c = prefix_len + 1;
1226                 while (sub[c] && sub[c] != ']')
1227                         ++c;
1228                 if (sub[c])
1229                         return 0; /* no end to the ']' found */
1230                 else
1231                         prefix_len = c + 1;
1232         }
1233
1234         /* did we find the ':' ? */
1235         if (sub[prefix_len] == ':') {
1236                 ++prefix_len;
1237                 if (sub[prefix_len] == ' ')
1238                         ++prefix_len;
1239                 prefix_len += modest_text_utils_get_subject_prefix_len (sub + prefix_len);
1240 /*              g_warning ("['%s','%s']", sub, (char*) sub + prefix_len); */
1241                 return prefix_len;
1242         } else
1243                 return 0;
1244 }
1245
1246
1247 gint
1248 modest_text_utils_utf8_strcmp (const gchar* s1, const gchar *s2, gboolean insensitive)
1249 {
1250
1251 /* work even when s1 and/or s2 == NULL */
1252         if (G_UNLIKELY(s1 == s2))
1253                 return 0;
1254         if (G_UNLIKELY(!s1))
1255                 return -1;
1256         if (G_UNLIKELY(!s2))
1257                 return 1;
1258         
1259         /* if it's not case sensitive */
1260         if (!insensitive) {
1261
1262                 /* optimization: shortcut if first char is ascii */ 
1263                 if (((s1[0] & 0xf0)== 0) && ((s2[0] & 0xf0) == 0)) 
1264                         return s1[0] - s2[0];
1265                 
1266                 return g_utf8_collate (s1, s2);
1267
1268         } else {
1269                 gint result;
1270                 gchar *n1, *n2;
1271
1272                 /* optimization: short cut iif first char is ascii */ 
1273                 if (((s1[0] & 0xf0) == 0) && ((s2[0] & 0xf0) == 0)) 
1274                         return tolower(s1[0]) - tolower(s2[0]);
1275                 
1276                 n1 = g_utf8_strdown (s1, -1);
1277                 n2 = g_utf8_strdown (s2, -1);
1278                 
1279                 result = g_utf8_collate (n1, n2);
1280                 
1281                 g_free (n1);
1282                 g_free (n2);
1283         
1284                 return result;
1285         }
1286 }
1287
1288
1289 const gchar*
1290 modest_text_utils_get_display_date (time_t date)
1291 {
1292 #define DATE_BUF_SIZE 64 
1293         static gchar date_buf[DATE_BUF_SIZE];
1294         
1295         /* calculate the # of days since epoch for 
1296          * for today and for the date provided 
1297          * based on idea from pvanhoof */
1298         int day      = time(NULL) / (24 * 60 * 60);
1299         int date_day = date       / (24 * 60 * 60);
1300
1301         /* if it's today, show the time, if it's not today, show the date instead */
1302
1303         if (day == date_day) /* is the date today? */
1304                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%X", date);
1305         else 
1306                 modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%x", date); 
1307
1308         return date_buf; /* this is a static buffer, don't free! */
1309 }
1310
1311
1312
1313 gboolean
1314 modest_text_utils_validate_folder_name (const gchar *folder_name)
1315 {
1316         /* based on http://msdn2.microsoft.com/en-us/library/aa365247.aspx,
1317          * with some extras */
1318         
1319         guint len;
1320         gint i;
1321         const gchar **cursor = NULL;
1322         const gchar *forbidden_names[] = { /* windows does not like these */
1323                 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6",
1324                 "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
1325                 ".", "..", "cur", "tmp", "new", NULL /* cur, tmp new  are reserved for Maildir */
1326         };
1327         
1328         /* cannot be NULL */
1329         if (!folder_name) 
1330                 return FALSE;
1331
1332         /* cannot be empty */
1333         len = strlen(folder_name);
1334         if (len == 0)
1335                 return FALSE;
1336         
1337         /* cannot start with a dot, vfat does not seem to like that */
1338         if (folder_name[0] == '.')
1339                 return FALSE;
1340
1341         /* cannot start or end with a space */
1342         if (g_ascii_isspace(folder_name[0]) || g_ascii_isspace(folder_name[len - 1]))
1343                 return FALSE; 
1344
1345         /* cannot contain a forbidden char */   
1346         for (i = 0; i < len; i++)
1347                 if (modest_text_utils_is_forbidden_char (folder_name[i], FOLDER_NAME_FORBIDDEN_CHARS))
1348                         return FALSE;
1349         
1350         /* cannot contain a forbidden word */
1351         if (len <= 4) {
1352                 for (cursor = forbidden_names; cursor && *cursor; ++cursor) {
1353                         if (g_ascii_strcasecmp (folder_name, *cursor) == 0)
1354                                 return FALSE;
1355                 }
1356         }
1357
1358         return TRUE; /* it's valid! */
1359 }
1360
1361
1362
1363 gboolean
1364 modest_text_utils_validate_domain_name (const gchar *domain)
1365 {
1366         gboolean valid = FALSE;
1367         regex_t rx;
1368         const gchar* domain_regex = "^([a-z0-9-]*[a-z0-9]\\.)+[a-z0-9-]*[a-z0-9]$";
1369
1370         g_return_val_if_fail (domain, FALSE);
1371         
1372         if (!domain)
1373                 return FALSE;
1374         
1375         memset (&rx, 0, sizeof(regex_t)); /* coverity wants this... */
1376                 
1377         /* domain name: all alphanum or '-' or '.',
1378          * but beginning/ending in alphanum */  
1379         if (regcomp (&rx, domain_regex, REG_ICASE|REG_EXTENDED|REG_NOSUB)) {
1380                 g_warning ("BUG: error in regexp");
1381                 return FALSE;
1382         }
1383         
1384         valid = (regexec (&rx, domain, 1, NULL, 0) == 0);
1385         regfree (&rx);
1386                 
1387         return valid;
1388 }
1389
1390
1391
1392 gboolean
1393 modest_text_utils_validate_email_address (const gchar *email_address,
1394                                           const gchar **invalid_char_position)
1395 {
1396         int count = 0;
1397         const gchar *c = NULL, *domain = NULL;
1398         static gchar *rfc822_specials = "()<>@,;:\\\"[]&";
1399         
1400         if (invalid_char_position)
1401                 *invalid_char_position = NULL;
1402         
1403         g_return_val_if_fail (email_address, FALSE);
1404         
1405         /* check that the email adress contains exactly one @ */
1406         if (!strstr(email_address, "@") || 
1407                         (strstr(email_address, "@") != g_strrstr(email_address, "@"))) 
1408                 return FALSE;
1409         
1410         /* first we validate the name portion (name@domain) */
1411         for (c = email_address;  *c;  c++) {
1412                 if (*c == '\"' && 
1413                     (c == email_address || 
1414                      *(c - 1) == '.' || 
1415                      *(c - 1) == '\"')) {
1416                         while (*++c) {
1417                                 if (*c == '\"') 
1418                                         break;
1419                                 if (*c == '\\' && (*++c == ' ')) 
1420                                         continue;
1421                                 if (*c <= ' ' || *c >= 127) 
1422                                         return FALSE;
1423                         }
1424                         if (!*c++) 
1425                                 return FALSE;
1426                         if (*c == '@') 
1427                                 break;
1428                         if (*c != '.') 
1429                                 return FALSE;
1430                         continue;
1431                 }
1432                 if (*c == '@') 
1433                         break;
1434                 if (*c <= ' ' || *c >= 127) 
1435                         return FALSE;
1436                 if (strchr(rfc822_specials, *c)) {
1437                         if (invalid_char_position)
1438                                 *invalid_char_position = c;
1439                         return FALSE;
1440                 }
1441         }
1442         if (c == email_address || *(c - 1) == '.') 
1443                 return FALSE;
1444
1445         /* next we validate the domain portion (name@domain) */
1446         if (!*(domain = ++c)) 
1447                 return FALSE;
1448         do {
1449                 if (*c == '.') {
1450                         if (c == domain || *(c - 1) == '.' || *(c + 1) == '\0') 
1451                                 return FALSE;
1452                         count++;
1453                 }
1454                 if (*c <= ' ' || *c >= 127) 
1455                         return FALSE;
1456                 if (strchr(rfc822_specials, *c)) {
1457                         if (invalid_char_position)
1458                                 *invalid_char_position = c;
1459                         return FALSE;
1460                 }
1461         } while (*++c);
1462
1463         return (count >= 1) ? TRUE : FALSE;
1464 }
1465
1466 gboolean 
1467 modest_text_utils_validate_recipient (const gchar *recipient, const gchar **invalid_char_position)
1468 {
1469         gchar *stripped, *current;
1470         gchar *right_part;
1471         gboolean has_error = FALSE;
1472
1473         if (invalid_char_position)
1474                 *invalid_char_position = NULL;
1475         
1476         g_return_val_if_fail (recipient, FALSE);
1477         
1478         if (modest_text_utils_validate_email_address (recipient, invalid_char_position))
1479                 return TRUE;
1480
1481         stripped = g_strdup (recipient);
1482         stripped = g_strstrip (stripped);
1483         current = stripped;
1484
1485         if (*current == '\0') {
1486                 g_free (stripped);
1487                 return FALSE;
1488         }
1489
1490         /* quoted string */
1491         if (*current == '\"') {
1492                 current = g_utf8_next_char (current);
1493                 has_error = TRUE;
1494                 for (; *current != '\0'; current = g_utf8_next_char (current)) {
1495                         if (*current == '\\') {
1496                                 /* TODO: This causes a warning, which breaks the build, 
1497                                  * because a gchar cannot be < 0.
1498                                  * murrayc. 
1499                                 if (current[1] <0) {
1500                                         has_error = TRUE;
1501                                         break;
1502                                 }
1503                                 */
1504                         } else if (*current == '\"') {
1505                                 has_error = FALSE;
1506                                 current = g_utf8_next_char (current);
1507                                 break;
1508                         }
1509                 }
1510         } else {
1511                 has_error = TRUE;
1512                 for (current = stripped ; *current != '\0'; current = g_utf8_next_char (current)) {
1513                         if (*current == '<') {
1514                                 has_error = FALSE;
1515                                 break;
1516                         }
1517                 }
1518         }
1519                 
1520         if (has_error) {
1521                 g_free (stripped);
1522                 return FALSE;
1523         }
1524
1525         right_part = g_strdup (current);
1526         g_free (stripped);
1527         right_part = g_strstrip (right_part);
1528
1529         if (g_str_has_prefix (right_part, "<") &&
1530             g_str_has_suffix (right_part, ">")) {
1531                 gchar *address;
1532                 gboolean valid;
1533
1534                 address = g_strndup (right_part+1, strlen (right_part) - 2);
1535                 g_free (right_part);
1536                 valid = modest_text_utils_validate_email_address (address, invalid_char_position);
1537                 g_free (address);
1538                 return valid;
1539         } else {
1540                 g_free (right_part);
1541                 return FALSE;
1542         }
1543 }
1544
1545
1546 gchar *
1547 modest_text_utils_get_display_size (guint64 size)
1548 {
1549         const guint KB=1024;
1550         const guint MB=1024 * KB;
1551         const guint GB=1024 * MB;
1552
1553         if (size == 0)
1554                 return g_strdup_printf(_FM("sfil_li_size_kb"), 0);
1555         if (0 < size && size < KB)
1556                 return g_strdup_printf (_FM("sfil_li_size_kb"), 1);
1557         else if (KB <= size && size < 100 * KB)
1558                 return g_strdup_printf (_FM("sfil_li_size_1kb_99kb"), size / KB);
1559         else if (100*KB <= size && size < MB)
1560                 return g_strdup_printf (_FM("sfil_li_size_100kb_1mb"), (float) size / MB);
1561         else if (MB <= size && size < 10*MB)
1562                 return g_strdup_printf (_FM("sfil_li_size_1mb_10mb"), (float) size / MB);
1563         else if (10*MB <= size && size < GB)
1564                 return g_strdup_printf (_FM("sfil_li_size_10mb_1gb"), size / MB);
1565         else
1566                 return g_strdup_printf (_FM("sfil_li_size_1gb_or_greater"), (float) size / GB); 
1567 }
1568
1569 static gchar *
1570 get_email_from_address (const gchar * address)
1571 {
1572         gchar *left_limit, *right_limit;
1573
1574         left_limit = strstr (address, "<");
1575         right_limit = g_strrstr (address, ">");
1576
1577         if ((left_limit == NULL)||(right_limit == NULL)|| (left_limit > right_limit))
1578                 return g_strdup (address);
1579         else
1580                 return g_strndup (left_limit + 1, (right_limit - left_limit) - 1);
1581 }
1582
1583 gchar *      
1584 modest_text_utils_get_color_string (GdkColor *color)
1585 {
1586         g_return_val_if_fail (color, NULL);
1587         
1588         return g_strdup_printf ("#%x%x%x%x%x%x%x%x%x%x%x%x",
1589                                 (color->red >> 12)   & 0xf, (color->red >> 8)   & 0xf,
1590                                 (color->red >>  4)   & 0xf, (color->red)        & 0xf,
1591                                 (color->green >> 12) & 0xf, (color->green >> 8) & 0xf,
1592                                 (color->green >>  4) & 0xf, (color->green)      & 0xf,
1593                                 (color->blue >> 12)  & 0xf, (color->blue >> 8)  & 0xf,
1594                                 (color->blue >>  4)  & 0xf, (color->blue)       & 0xf);
1595 }
1596
1597 gchar *
1598 modest_text_utils_text_buffer_get_text (GtkTextBuffer *buffer)
1599 {
1600         GtkTextIter start, end;
1601         gchar *slice, *current;
1602         GString *result = g_string_new ("");
1603
1604         g_return_val_if_fail (buffer && GTK_IS_TEXT_BUFFER (buffer), NULL);
1605         
1606         gtk_text_buffer_get_start_iter (buffer, &start);
1607         gtk_text_buffer_get_end_iter (buffer, &end);
1608
1609         slice = gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
1610         current = slice;
1611
1612         while (current && current != '\0') {
1613                 if (g_utf8_get_char (current) == 0xFFFC) {
1614                         result = g_string_append_c (result, ' ');
1615                         current = g_utf8_next_char (current);
1616                 } else {
1617                         gchar *next = g_utf8_strchr (current, -1, 0xFFFC);
1618                         if (next == NULL) {
1619                                 result = g_string_append (result, current);
1620                         } else {
1621                                 result = g_string_append_len (result, current, next - current);
1622                         }
1623                         current = next;
1624                 }
1625         }
1626         g_free (slice);
1627
1628         return g_string_free (result, FALSE);
1629         
1630 }
1631
1632 gboolean
1633 modest_text_utils_is_forbidden_char (const gchar character,
1634                                      ModestTextUtilsForbiddenCharType type)
1635 {
1636         gint i, len;
1637         const gchar *forbidden_chars = NULL;
1638         
1639         /* We need to get the length in the switch because the
1640            compiler needs to know the size at compile time */
1641         switch (type) {
1642         case ACCOUNT_TITLE_FORBIDDEN_CHARS:
1643                 forbidden_chars = account_title_forbidden_chars;
1644                 len = G_N_ELEMENTS (account_title_forbidden_chars);
1645                 break;
1646         case FOLDER_NAME_FORBIDDEN_CHARS:
1647                 forbidden_chars = folder_name_forbidden_chars;
1648                 len = G_N_ELEMENTS (folder_name_forbidden_chars);
1649                 break;
1650         case USER_NAME_FORBIDDEN_NAMES:
1651                 forbidden_chars = user_name_forbidden_chars;
1652                 len = G_N_ELEMENTS (user_name_forbidden_chars);
1653                 break;
1654         default:
1655                 g_return_val_if_reached (TRUE);
1656         }
1657
1658         for (i = 0; i < len ; i++)
1659                 if (forbidden_chars[i] == character)
1660                         return TRUE;
1661
1662         return FALSE; /* it's valid! */
1663 }
1664
1665 gchar *      
1666 modest_text_utils_label_get_selection (GtkLabel *label)
1667 {
1668         gint start, end;
1669         gchar *selection;
1670
1671         if (gtk_label_get_selection_bounds (GTK_LABEL (label), &start, &end)) {
1672                 const gchar *start_offset;
1673                 const gchar *end_offset;
1674                 start_offset = gtk_label_get_text (GTK_LABEL (label));
1675                 start_offset = g_utf8_offset_to_pointer (start_offset, start);
1676                 end_offset = gtk_label_get_text (GTK_LABEL (label));
1677                 end_offset = g_utf8_offset_to_pointer (end_offset, end);
1678                 selection = g_strndup (start_offset, end_offset - start_offset);
1679                 return selection;
1680         } else {
1681                 return g_strdup ("");
1682         }
1683 }
1684
1685 static gboolean
1686 _forward_search_image_char (gunichar ch,
1687                             gpointer userdata)
1688 {
1689         return (ch == 0xFFFC);
1690 }
1691
1692 gboolean
1693 modest_text_utils_buffer_selection_is_valid (GtkTextBuffer *buffer)
1694 {
1695         gboolean result;
1696         GtkTextIter start, end;
1697
1698         g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
1699
1700         result = gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (buffer));
1701
1702         /* check there are no images in selection */
1703         if (result) {
1704                 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
1705                 if (gtk_text_iter_get_char (&start)== 0xFFFC)
1706                         result = FALSE;
1707                 else {
1708                         gtk_text_iter_backward_char (&end);
1709                         if (gtk_text_iter_forward_find_char (&start, _forward_search_image_char,
1710                                                              NULL, &end))
1711                                 result = FALSE;
1712                 }
1713                                     
1714         }
1715
1716         return result;
1717 }
1718
1719 gchar *
1720 modest_text_utils_escape_mnemonics (const gchar *text)
1721 {
1722         const gchar *p;
1723         GString *result = NULL;
1724
1725         if (text == NULL)
1726                 return NULL;
1727
1728         result = g_string_new ("");
1729         for (p = text; *p != '\0'; p++) {
1730                 if (*p == '_')
1731                         result = g_string_append (result, "__");
1732                 else
1733                         result = g_string_append_c (result, *p);
1734         }
1735         
1736         return g_string_free (result, FALSE);
1737 }