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