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