X-Git-Url: http://git.maemo.org/git/?p=modest;a=blobdiff_plain;f=src%2Fmodest-text-utils.c;h=2b7cbb5abee79cbd60f180ae1dc9a97a1150a095;hp=973b19ea13be3ded37d62dc0709b37bc9b0e494d;hb=28fbc4e0d124d6c3d41cfd970ad699fe48555182;hpb=6f839c90aa26fbc42532cb8ee1bd4769f8539a14 diff --git a/src/modest-text-utils.c b/src/modest-text-utils.c index 973b19e..2b7cbb5 100644 --- a/src/modest-text-utils.c +++ b/src/modest-text-utils.c @@ -28,29 +28,43 @@ */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif /*_GNU_SOURCE*/ +#include /* for strcasestr */ + + #include -#include #include #include #include #include #include #include - +#include #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H */ /* defines */ -#define FORWARD_STRING _("-----Forwarded Message-----") -#define FROM_STRING _("From:") -#define SENT_STRING _("Sent:") -#define TO_STRING _("To:") -#define SUBJECT_STRING _("Subject:") +#define FORWARD_STRING _("mcen_ia_editor_original_message") +#define FROM_STRING _("mail_va_from") +#define SENT_STRING _("mcen_fi_message_properties_sent") +#define TO_STRING _("mail_va_to") +#define SUBJECT_STRING _("mail_va_subject") #define EMPTY_STRING "" /* + * do the hyperlinkification only for texts < 50 Kb, + * as it's quite slow. Without this, e.g. mail with + * an uuencoded part (which is not recognized as attachment, + * will hang modest + */ +#define HYPERLINKIFY_MAX_LENGTH (1024*50) + +/* * we need these regexps to find URLs in plain text e-mails */ typedef struct _url_match_pattern_t url_match_pattern_t; @@ -67,14 +81,40 @@ struct _url_match_t { const gchar* prefix; }; + +/* + * we mark the ampersand with \007 when converting text->html + * because after text->html we do hyperlink detecting, which + * could be screwed up by the ampersand. + * ie. 1<3 ==> 1\007lt;3 + */ +#define MARK_AMP '\007' +#define MARK_AMP_STR "\007" + +/* mark & separately, because they are parts of urls. + * ie. a&b => a\006amp;b, but a>b => a\007gt;b + * + * we need to handle '&' separately, because it can be part of URIs + * (as in href="http://foo.bar?a=1&b=1"), so inside those URIs + * we need to re-replace \006amp; with '&' again, while outside uri's + * it will be '&' + * + * yes, it's messy, but a consequence of doing text->html first, then hyperlinkify + */ +#define MARK_AMP_URI '\006' +#define MARK_AMP_URI_STR "\006" + + +/* note: match MARK_AMP_URI_STR as well, because after txt->html, a '&' will look like $(MARK_AMP_URI_STR)"amp;" */ #define MAIL_VIEWER_URL_MATCH_PATTERNS { \ - { "(file|rtsp|http|ftp|https)://[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]+[-A-Za-z0-9_$%&=?/~#]",\ + { "(feed:|)(file|rtsp|http|ftp|https|mms|mmsh|webcal|feed|rtsp|rdp|lastfm|sip)://[-a-z0-9_$.+!*(),;:@%=\?/~#&" MARK_AMP_URI_STR \ + "]+[-a-z0-9_$%&" MARK_AMP_URI_STR "=?/~#]", \ NULL, NULL },\ - { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\ - NULL, "http://" },\ - { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\ + { "www\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",\ + NULL, "http://" }, \ + { "ftp\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",\ NULL, "ftp://" },\ - { "(voipto|callto|chatto|jabberto|xmpp):[-_a-z@0-9.\\+]+", \ + { "(jabberto|voipto|sipto|sip|chatto|skype|xmpp):[-_a-z@0-9.+]+", \ NULL, NULL}, \ { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+", \ NULL, NULL},\ @@ -82,17 +122,30 @@ struct _url_match_t { NULL, "mailto:"}\ } +const gchar account_title_forbidden_chars[] = { + '\\', '/', ':', '*', '?', '\'', '<', '>', '|', '^' +}; +const gchar folder_name_forbidden_chars[] = { + '<', '>', ':', '\'', '/', '\\', '|', '?', '*', '^', '%', '$', '#', '&' +}; +const gchar user_name_forbidden_chars[] = { + '<', '>' +}; +const guint ACCOUNT_TITLE_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (account_title_forbidden_chars); +const guint FOLDER_NAME_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (folder_name_forbidden_chars); +const guint USER_NAME_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (user_name_forbidden_chars); + /* private */ static gchar* cite (const time_t sent_date, const gchar *from); -static void hyperlinkify_plain_text (GString *txt); +static void hyperlinkify_plain_text (GString *txt, gint offset); static gint cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2); -static void chk_partial_match (const url_match_t *match, guint* offset); -static GSList* get_url_matches (GString *txt); +static GSList* get_url_matches (GString *txt, gint offset); static GString* get_next_line (const char *b, const gsize blen, const gchar * iter); static int get_indent_level (const char *l); -static void unquote_line (GString * l); -static void append_quoted (GString * buf, const int indent, const GString * str, +static void unquote_line (GString * l, const gchar *quote_symbol); +static void append_quoted (GString * buf, const gchar *quote_symbol, + const int indent, const GString * str, const int cutpoint); static int get_breakpoint_utf8 (const gchar * s, const gint indent, const gint limit); static int get_breakpoint_ascii (const gchar * s, const gint indent, const gint limit); @@ -100,11 +153,16 @@ static int get_breakpoint (const gchar * s, const gint indent, con static gchar* modest_text_utils_quote_plain_text (const gchar *text, const gchar *cite, + const gchar *signature, + GList *attachments, int limit); static gchar* modest_text_utils_quote_html (const gchar *text, - const gchar *cite, + const gchar *cite, + const gchar *signature, + GList *attachments, int limit); +static gchar* get_email_from_address (const gchar *address); /* ******************************************************************* */ @@ -117,6 +175,7 @@ modest_text_utils_quote (const gchar *text, const gchar *signature, const gchar *from, const time_t sent_date, + GList *attachments, int limit) { gchar *retval, *cited; @@ -129,12 +188,12 @@ modest_text_utils_quote (const gchar *text, if (content_type && strcmp (content_type, "text/html") == 0) /* TODO: extract the of the HTML and pass it to the function */ - retval = modest_text_utils_quote_html (text, cited, limit); + retval = modest_text_utils_quote_html (text, cited, signature, attachments, limit); else - retval = modest_text_utils_quote_plain_text (text, cited, limit); + retval = modest_text_utils_quote_plain_text (text, cited, signature, attachments, limit); g_free (cited); - + return retval; } @@ -146,28 +205,44 @@ modest_text_utils_cite (const gchar *text, const gchar *from, time_t sent_date) { - gchar *tmp, *retval; + gchar *retval; gchar *tmp_sig; - + g_return_val_if_fail (text, NULL); g_return_val_if_fail (content_type, NULL); - - if (!signature) - tmp_sig = g_strdup (""); - else if (!strcmp(content_type, "text/html")) { - tmp_sig = modest_text_utils_convert_to_html_body(signature); + + if (!signature) { + tmp_sig = g_strdup (text); } else { - tmp_sig = g_strdup (signature); + tmp_sig = g_strconcat (text, "\n", MODEST_TEXT_UTILS_SIGNATURE_MARKER, "\n", signature, NULL); } - tmp = cite (sent_date, from); - retval = g_strdup_printf ("%s%s%s\n", tmp_sig, tmp, text); - g_free (tmp_sig); - g_free (tmp); + if (strcmp (content_type, "text/html") == 0) { + retval = modest_text_utils_convert_to_html_body (tmp_sig, -1, TRUE); + g_free (tmp_sig); + } else { + retval = tmp_sig; + } return retval; } +static gchar * +forward_cite (const gchar *from, + const gchar *sent, + const gchar *to, + const gchar *subject) +{ + g_return_val_if_fail (sent, NULL); + + return g_strdup_printf ("%s\n%s %s\n%s %s\n%s %s\n%s %s\n", + FORWARD_STRING, + FROM_STRING, (from)?from:"", + SENT_STRING, sent, + TO_STRING, (to)?to:"", + SUBJECT_STRING, (subject)?subject:""); +} + gchar * modest_text_utils_inline (const gchar *text, const gchar *content_type, @@ -178,47 +253,23 @@ modest_text_utils_inline (const gchar *text, const gchar *subject) { gchar sent_str[101]; - gchar *formatted_signature; - const gchar *plain_format = "%s%s\n%s %s\n%s %s\n%s %s\n%s %s\n\n%s"; - const gchar *html_format = \ - "%s%s
\n\n" \ - "\n" \ - "\n" \ - "\n" \ - "\n" \ - "

%s"; - const gchar *format; - + gchar *cited; + gchar *retval; + g_return_val_if_fail (text, NULL); g_return_val_if_fail (content_type, NULL); - g_return_val_if_fail (text, NULL); modest_text_utils_strftime (sent_str, 100, "%c", sent_date); - if (!strcmp (content_type, "text/html")) - /* TODO: extract the of the HTML and pass it to - the function */ - format = html_format; + cited = forward_cite (from, sent_str, to, subject); + + if (content_type && strcmp (content_type, "text/html") == 0) + retval = modest_text_utils_quote_html (text, cited, signature, NULL, 80); else - format = plain_format; - - if (signature != NULL) { - if (!strcmp (content_type, "text/html")) { - formatted_signature = g_strconcat (signature, "
", NULL); - } else { - formatted_signature = g_strconcat (signature, "\n", NULL); - } - } else { - formatted_signature = ""; - } - - return g_strdup_printf (format, formatted_signature, - FORWARD_STRING, - FROM_STRING, (from) ? from : EMPTY_STRING, - SENT_STRING, sent_str, - TO_STRING, (to) ? to : EMPTY_STRING, - SUBJECT_STRING, (subject) ? subject : EMPTY_STRING, - text); + retval = modest_text_utils_quote_plain_text (text, cited, signature, NULL, 80); + + g_free (cited); + return retval; } /* just to prevent warnings: @@ -229,48 +280,71 @@ modest_text_utils_strftime(char *s, gsize max, const char *fmt, time_t timet) { struct tm tm; + /* To prevent possible problems in strftime that could leave + garbage in the s variable */ + if (s) + s[0] = '\0'; + else + return 0; + /* does not work on old maemo glib: * g_date_set_time_t (&date, timet); */ localtime_r (&timet, &tm); - return strftime(s, max, fmt, &tm); } gchar * modest_text_utils_derived_subject (const gchar *subject, const gchar *prefix) { - gchar *tmp; + gchar *tmp, *subject_dup, *retval; + gint prefix_len; g_return_val_if_fail (prefix, NULL); - - if (!subject) - return g_strdup (prefix); - tmp = g_strchug (g_strdup (subject)); + if (!subject || subject[0] == '\0') + subject = _("mail_va_no_subject"); - if (!strncmp (tmp, prefix, strlen (prefix))) { - return tmp; - } else { - g_free (tmp); - return g_strdup_printf ("%s %s", prefix, subject); - } + subject_dup = g_strdup (subject); + tmp = g_strchug (subject_dup); + + /* We do not want things like "Re: Re: Re:" or "Fw: Fw:" so + delete the previous ones */ + prefix_len = strlen (prefix); + do { + if (g_str_has_prefix (tmp, prefix)) { + tmp += prefix_len; + tmp = g_strchug (tmp); + } else { + break; + } + } while (tmp); + + retval = g_strdup_printf ("%s %s", prefix, tmp); + g_free (subject_dup); + + return retval; } gchar* modest_text_utils_remove_address (const gchar *address_list, const gchar *address) { - gchar *dup, *token, *ptr, *result; + gchar *dup, *token, *ptr = NULL, *result; GString *filtered_emails; + gchar *email_address; g_return_val_if_fail (address_list, NULL); - + if (!address) return g_strdup (address_list); + + email_address = get_email_from_address (address); /* search for substring */ - if (!strstr ((const char *) address_list, (const char *) address)) + if (!strstr ((const char *) address_list, (const char *) email_address)) { + g_free (email_address); return g_strdup (address_list); + } dup = g_strdup (address_list); filtered_emails = g_string_new (NULL); @@ -279,7 +353,7 @@ modest_text_utils_remove_address (const gchar *address_list, const gchar *addres while (token != NULL) { /* Add to list if not found */ - if (!strstr ((const char *) token, (const char *) address)) { + if (!strstr ((const char *) token, (const char *) email_address)) { if (filtered_emails->len == 0) g_string_append_printf (filtered_emails, "%s", g_strstrip (token)); else @@ -290,42 +364,117 @@ modest_text_utils_remove_address (const gchar *address_list, const gchar *addres result = filtered_emails->str; /* Clean */ + g_free (email_address); g_free (dup); g_string_free (filtered_emails, FALSE); return result; } + +gchar* +modest_text_utils_remove_duplicate_addresses (const gchar *address_list) +{ + GSList *addresses, *cursor; + GHashTable *table; + gchar *new_list = NULL; + + g_return_val_if_fail (address_list, NULL); + + table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + addresses = modest_text_utils_split_addresses_list (address_list); + + cursor = addresses; + while (cursor) { + const gchar* address = (const gchar*)cursor->data; + + /* We need only the email to just compare it and not + the full address which would make "a " + different from "a@a.com" */ + const gchar *email = get_email_from_address (address); + + /* ignore the address if already seen */ + if (g_hash_table_lookup (table, email) == 0) { + gchar *tmp; + + /* Include the full address and not only the + email in the returned list */ + if (!new_list) { + tmp = g_strdup (address); + } else { + tmp = g_strjoin (",", new_list, address, NULL); + g_free (new_list); + } + new_list = tmp; + + g_hash_table_insert (table, (gchar*)email, GINT_TO_POINTER(1)); + } + cursor = g_slist_next (cursor); + } + + g_hash_table_unref (table); + g_slist_foreach (addresses, (GFunc)g_free, NULL); + g_slist_free (addresses); + + if (new_list == NULL) + new_list = g_strdup (""); + + return new_list; +} + + static void -modest_text_utils_convert_buffer_to_html (GString *html, const gchar *data) +modest_text_utils_convert_buffer_to_html_start (GString *html, const gchar *data, gssize n) { - guint i; + guint i; gboolean space_seen = FALSE; - gsize len; + guint break_dist = 0; /* distance since last break point */ - len = strlen (data); + if (n == -1) + n = strlen (data); /* replace with special html chars where needed*/ - for (i = 0; i != len; ++i) { - char kar = data[i]; + for (i = 0; i != n; ++i) { + guchar kar = data[i]; if (space_seen && kar != ' ') { - g_string_append_c (html, ' '); + g_string_append (html, " "); space_seen = FALSE; } + /* we artificially insert a breakpoint (newline) + * after 256, to make sure our lines are not so long + * they will DOS the regexping later + * Also, check that kar is ASCII to make sure that we + * don't break a UTF8 char in two + */ + if (++break_dist >= 256 && kar < 127) { + g_string_append_c (html, '\n'); + break_dist = 0; + } + switch (kar) { - case 0: break; /* ignore embedded \0s */ - case '<' : g_string_append (html, "<"); break; - case '>' : g_string_append (html, ">"); break; - case '&' : g_string_append (html, "&"); break; - case '"' : g_string_append (html, """); break; - case '\'' : g_string_append (html, "'"); break; - case '\n' : g_string_append (html, "
\n"); break; - case '\t' : g_string_append (html, "    "); break; /* note the space at the end*/ + case 0: + case MARK_AMP: + case MARK_AMP_URI: + /* this is a temp place holder for '&'; we can only + * set the real '&' after hyperlink translation, otherwise + * we might screw that up */ + break; /* ignore embedded \0s and MARK_AMP */ + case '<' : g_string_append (html, MARK_AMP_STR "lt;"); break; + case '>' : g_string_append (html, MARK_AMP_STR "gt;"); break; + case '&' : g_string_append (html, MARK_AMP_URI_STR "amp;"); break; /* special case */ + case '"' : g_string_append (html, MARK_AMP_STR "quot;"); break; + + /* don't convert ' --> wpeditor will try to re-convert it... */ + //case '\'' : g_string_append (html, "'"); break; + case '\n' : g_string_append (html, "
\n");break_dist= 0; break; + case '\t' : g_string_append (html, MARK_AMP_STR "nbsp;" MARK_AMP_STR "nbsp;" MARK_AMP_STR "nbsp; "); + break_dist=0; break; /* note the space at the end*/ case ' ': + break_dist = 0; if (space_seen) { /* second space in a row */ - g_string_append (html, "  "); + g_string_append (html, "  "); space_seen = FALSE; } else space_seen = TRUE; @@ -336,11 +485,25 @@ modest_text_utils_convert_buffer_to_html (GString *html, const gchar *data) } } + +static void +modest_text_utils_convert_buffer_to_html_finish (GString *html) +{ + int i; + /* replace all our MARK_AMPs with real ones */ + for (i = 0; i != html->len; ++i) + if ((html->str)[i] == MARK_AMP || (html->str)[i] == MARK_AMP_URI) + (html->str)[i] = '&'; +} + + gchar* modest_text_utils_convert_to_html (const gchar *data) { GString *html; gsize len; + + g_return_val_if_fail (data, NULL); if (!data) return NULL; @@ -354,30 +517,39 @@ modest_text_utils_convert_to_html (const gchar *data) "" ""); - modest_text_utils_convert_buffer_to_html (html, data); + modest_text_utils_convert_buffer_to_html_start (html, data, -1); g_string_append (html, ""); - hyperlinkify_plain_text (html); + if (len <= HYPERLINKIFY_MAX_LENGTH) + hyperlinkify_plain_text (html, 0); + + modest_text_utils_convert_buffer_to_html_finish (html); + return g_string_free (html, FALSE); } gchar * -modest_text_utils_convert_to_html_body (const gchar *data) +modest_text_utils_convert_to_html_body (const gchar *data, gssize n, gboolean hyperlinkify) { GString *html; - gsize len; - + + g_return_val_if_fail (data, NULL); + if (!data) return NULL; - len = strlen (data); - html = g_string_sized_new (1.5 * len); /* just a guess... */ + if (n == -1) + n = strlen (data); + html = g_string_sized_new (1.5 * n); /* just a guess... */ - modest_text_utils_convert_buffer_to_html (html, data); + modest_text_utils_convert_buffer_to_html_start (html, data, n); - hyperlinkify_plain_text (html); + if (hyperlinkify && (n < HYPERLINKIFY_MAX_LENGTH)) + hyperlinkify_plain_text (html, 0); + modest_text_utils_convert_buffer_to_html_finish (html); + return g_string_free (html, FALSE); } @@ -439,54 +611,60 @@ modest_text_utils_get_addresses_indexes (const gchar *addresses, GSList **start_ return; } + GSList * modest_text_utils_split_addresses_list (const gchar *addresses) { - gchar *current, *start, *last_blank; - GSList *result = NULL; + GSList *head; + const gchar *my_addrs = addresses; + const gchar *end; + gchar *addr; + gboolean after_at = FALSE; - start = (gchar *) addresses; - current = start; - last_blank = start; + g_return_val_if_fail (addresses, NULL); + + /* skip any space, ',', ';' at the start */ + while (my_addrs && (my_addrs[0] == ' ' || my_addrs[0] == ',' || my_addrs[0] == ';')) + ++my_addrs; - while (*current != '\0') { - if ((start == current)&&((*current == ' ')||(*current == ',')||(*current == ';'))) { - start = g_utf8_next_char (start); - last_blank = current; - } else if ((*current == ',')||(*current == ';')) { - gchar *new_address = NULL; - new_address = g_strndup (start, current - last_blank); - result = g_slist_prepend (result, new_address); - start = g_utf8_next_char (current); - last_blank = start; - } else if (*current == '\"') { - if (current == start) { - current = g_utf8_next_char (current); - start = g_utf8_next_char (start); - } - while ((*current != '\"')&&(*current != '\0')) - current = g_utf8_next_char (current); + /* are we at the end of addresses list? */ + if (!my_addrs[0]) + return NULL; + + /* nope, we are at the start of some address + * now, let's find the end of the address */ + end = my_addrs + 1; + while (end[0] && end[0] != ';' && !(after_at && end[0] == ',')) { + if (end[0] == '\"') { + while (end[0] && end[0] != '\"') + ++end; } - - current = g_utf8_next_char (current); + if (end[0] == '@') { + after_at = TRUE; + } + if ((end[0] && end[0] == '>')&&(end[1] && end[1] == ',')) { + ++end; + break; + } + ++end; } - if (start != current) { - gchar *new_address = NULL; - new_address = g_strndup (start, current - last_blank); - result = g_slist_prepend (result, new_address); - } + /* we got the address; copy it and remove trailing whitespace */ + addr = g_strndup (my_addrs, end - my_addrs); + g_strchomp (addr); - result = g_slist_reverse (result); - return result; + head = g_slist_append (NULL, addr); + head->next = modest_text_utils_split_addresses_list (end); /* recurse */ + return head; } + void modest_text_utils_address_range_at_position (const gchar *recipients_list, - gint position, - gint *start, - gint *end) + guint position, + guint *start, + guint *end) { gchar *current = NULL; gint range_start = 0; @@ -494,8 +672,12 @@ modest_text_utils_address_range_at_position (const gchar *recipients_list, gint index; gboolean is_quoted = FALSE; + g_return_if_fail (recipients_list); + g_return_if_fail (position < g_utf8_strlen(recipients_list, -1)); + index = 0; - for (current = (gchar *) recipients_list; *current != '\0'; current = g_utf8_find_next_char (current, NULL)) { + for (current = (gchar *) recipients_list; *current != '\0'; + current = g_utf8_find_next_char (current, NULL)) { gunichar c = g_utf8_get_char (current); if ((c == ',') && (!is_quoted)) { @@ -519,6 +701,30 @@ modest_text_utils_address_range_at_position (const gchar *recipients_list, *end = range_end; } +gchar * +modest_text_utils_address_with_standard_length (const gchar *recipients_list) +{ + gchar ** splitted; + gchar ** current; + GString *buffer = g_string_new (""); + + splitted = g_strsplit (recipients_list, "\n", 0); + current = splitted; + while (*current) { + gchar *line; + if (current != splitted) + buffer = g_string_append_c (buffer, '\n'); + line = g_strndup (*splitted, 1000); + buffer = g_string_append (buffer, line); + g_free (line); + current++; + } + + g_strfreev (splitted); + + return g_string_free (buffer, FALSE); +} + /* ******************************************************************* */ /* ************************* UTILIY FUNCTIONS ************************ */ @@ -563,7 +769,7 @@ get_indent_level (const char *l) /* if we hit the signature marker "-- ", we return -(indent + 1). This * stops reformatting. */ - if (strcmp (l, "-- ") == 0) { + if (strcmp (l, MODEST_TEXT_UTILS_SIGNATURE_MARKER) == 0) { return -1 - indent; } else { return indent; @@ -571,15 +777,17 @@ get_indent_level (const char *l) } static void -unquote_line (GString * l) +unquote_line (GString * l, const gchar *quote_symbol) { gchar *p; + gint quote_len; p = l->str; + quote_len = strlen (quote_symbol); while (p[0]) { - if (p[0] == '>') { - if (p[1] == ' ') { - p++; + if (g_str_has_prefix (p, quote_symbol)) { + if (p[quote_len] == ' ') { + p += quote_len; } } else { break; @@ -590,15 +798,19 @@ unquote_line (GString * l) } static void -append_quoted (GString * buf, int indent, const GString * str, +append_quoted (GString * buf, const gchar *quote_symbol, + int indent, const GString * str, const int cutpoint) { int i; + gchar *quote_concat; indent = indent < 0 ? abs (indent) - 1 : indent; + quote_concat = g_strconcat (quote_symbol, " ", NULL); for (i = 0; i <= indent; i++) { - g_string_append (buf, "> "); + g_string_append (buf, quote_concat); } + g_free (quote_concat); if (cutpoint > 0) { g_string_append_len (buf, str->str, cutpoint); } else { @@ -670,37 +882,42 @@ get_breakpoint (const gchar * s, const gint indent, const gint limit) static gchar * cite (const time_t sent_date, const gchar *from) { - gchar sent_str[101]; - - /* format sent_date */ - modest_text_utils_strftime (sent_str, 100, "%c", sent_date); - return g_strdup_printf (N_("On %s, %s wrote:\n"), - sent_str, - (from) ? from : EMPTY_STRING); + return g_strdup (_("mcen_ia_editor_original_message")); } - static gchar * -modest_text_utils_quote_plain_text (const gchar *text, - const gchar *cite, - int limit) +quoted_attachments (GList *attachments) +{ + GList *node = NULL; + GString *result = g_string_new (""); + for (node = attachments; node != NULL; node = g_list_next (node)) { + gchar *filename = (gchar *) node->data; + g_string_append_printf ( result, "%s %s\n", _("mcen_ia_editor_attach_filename"), filename); + } + + return g_string_free (result, FALSE); + +} + +static GString * +modest_text_utils_quote_body (GString *output, const gchar *text, + const gchar *quote_symbol, + int limit) { + const gchar *iter; - gint indent, breakpoint, rem_indent = 0; - GString *q, *l, *remaining; gsize len; - - /* remaining will store the rest of the line if we have to break it */ - q = g_string_new (cite); - remaining = g_string_new (""); + gint indent, breakpoint, rem_indent = 0; + GString *l, *remaining; iter = text; len = strlen(text); + remaining = g_string_new (""); do { l = get_next_line (text, len, iter); iter = iter + l->len + 1; indent = get_indent_level (l->str); - unquote_line (l); + unquote_line (l, quote_symbol); if (remaining->len) { if (l->len && indent == rem_indent) { @@ -712,7 +929,7 @@ modest_text_utils_quote_plain_text (const gchar *text, get_breakpoint (remaining->str, rem_indent, limit); - append_quoted (q, rem_indent, + append_quoted (output, quote_symbol, rem_indent, remaining, breakpoint); g_string_erase (remaining, 0, breakpoint); @@ -730,28 +947,92 @@ modest_text_utils_quote_plain_text (const gchar *text, g_string_erase (remaining, 0, 1); } rem_indent = indent; - append_quoted (q, indent, l, breakpoint); + append_quoted (output, quote_symbol, indent, l, breakpoint); g_string_free (l, TRUE); } while ((iter < text + len) || (remaining->str[0])); + return output; +} + +static gchar * +modest_text_utils_quote_plain_text (const gchar *text, + const gchar *cite, + const gchar *signature, + GList *attachments, + int limit) +{ + GString *q; + gchar *attachments_string = NULL; + + q = g_string_new (""); + + if (signature != NULL) { + g_string_append_printf (q, "\n%s\n", MODEST_TEXT_UTILS_SIGNATURE_MARKER); + q = g_string_append (q, signature); + } + + q = g_string_append (q, "\n"); + q = g_string_append (q, cite); + q = g_string_append_c (q, '\n'); + + q = modest_text_utils_quote_body (q, text, ">", limit); + + attachments_string = quoted_attachments (attachments); + q = g_string_append (q, attachments_string); + g_free (attachments_string); + return g_string_free (q, FALSE); } +static void +quote_html_add_to_gstring (GString *string, + const gchar *text) +{ + if (text && strcmp (text, "")) { + gchar *html_text = modest_text_utils_convert_to_html_body (text, -1, TRUE); + g_string_append_printf (string, "%s
", html_text); + g_free (html_text); + } +} + static gchar* modest_text_utils_quote_html (const gchar *text, const gchar *cite, + const gchar *signature, + GList *attachments, int limit) { - const gchar *format = \ - "\n" \ - "\n" \ - "\n" \ - "%s" \ - "
\n%s\n
\n" \ - "\n" \ - "\n"; - - return g_strdup_printf (format, cite, text); + GString *result_string; + + result_string = + g_string_new ( \ + "\n" \ + "\n" \ + "\n
\n"); + + if (text || cite || signature) { + GString *quoted_text; + g_string_append (result_string, "
\n");
+		if (signature) {
+			quote_html_add_to_gstring (result_string, MODEST_TEXT_UTILS_SIGNATURE_MARKER);
+			quote_html_add_to_gstring (result_string, signature);
+		}
+		quote_html_add_to_gstring (result_string, cite);
+		quoted_text = g_string_new ("");
+		quoted_text = modest_text_utils_quote_body (quoted_text, (text) ? text : "", ">", limit);
+		quote_html_add_to_gstring (result_string, quoted_text->str);
+		g_string_free (quoted_text, TRUE);
+		if (attachments) {
+			gchar *attachments_string = quoted_attachments (attachments);
+			quote_html_add_to_gstring (result_string, attachments_string);
+			g_free (attachments_string);
+		}
+		g_string_append (result_string, "
"); + } + g_string_append (result_string, ""); + g_string_append (result_string, ""); + + return g_string_free (result_string, FALSE); } static gint @@ -760,69 +1041,117 @@ cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2) return match2->offset - match1->offset; } +static gint url_matches_block = 0; +static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS; +static GMutex *url_patterns_mutex = NULL; -/* - * check if the match is inside an existing match... */ -static void -chk_partial_match (const url_match_t *match, guint* offset) +static gboolean +compile_patterns () +{ + guint i; + const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t); + for (i = 0; i != pattern_num; ++i) { + patterns[i].preg = g_slice_new0 (regex_t); + + /* this should not happen */ + if (regcomp (patterns[i].preg, patterns[i].regex, + REG_ICASE|REG_EXTENDED|REG_NEWLINE) != 0) { + g_warning ("%s: error in regexp:\n%s\n", __FUNCTION__, patterns[i].regex); + return FALSE; + } + } + return TRUE; +} + +static void +free_patterns () +{ + guint i; + const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t); + for (i = 0; i != pattern_num; ++i) { + regfree (patterns[i].preg); + g_slice_free (regex_t, patterns[i].preg); + } /* don't free patterns itself -- it's static */ +} + +void +modest_text_utils_hyperlinkify_begin (void) +{ + + if (url_patterns_mutex == NULL) { + url_patterns_mutex = g_mutex_new (); + } + g_mutex_lock (url_patterns_mutex); + if (url_matches_block == 0) + compile_patterns (); + url_matches_block ++; + g_mutex_unlock (url_patterns_mutex); +} + +void +modest_text_utils_hyperlinkify_end (void) { - if (*offset >= match->offset && *offset < match->offset + match->len) - *offset = -1; + g_mutex_lock (url_patterns_mutex); + url_matches_block--; + if (url_matches_block <= 0) + free_patterns (); + g_mutex_unlock (url_patterns_mutex); } + static GSList* -get_url_matches (GString *txt) +get_url_matches (GString *txt, gint offset) { regmatch_t rm; - guint rv, i, offset = 0; + guint rv, i, tmp_offset = 0; GSList *match_list = NULL; - static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS; const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t); /* initalize the regexps */ - for (i = 0; i != pattern_num; ++i) { - patterns[i].preg = g_slice_new0 (regex_t); + modest_text_utils_hyperlinkify_begin (); - /* this should not happen */ - g_return_val_if_fail (regcomp (patterns[i].preg, patterns[i].regex, - REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0, NULL); - } /* find all the matches */ for (i = 0; i != pattern_num; ++i) { - offset = 0; + tmp_offset = offset; while (1) { - int test_offset; - if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) { + url_match_t *match; + gboolean is_submatch; + GSList *cursor; + + if ((rv = regexec (patterns[i].preg, txt->str + tmp_offset, 1, &rm, 0)) != 0) { g_return_val_if_fail (rv == REG_NOMATCH, NULL); /* this should not happen */ break; /* try next regexp */ } if (rm.rm_so == -1) break; - - /* FIXME: optimize this */ - /* to avoid partial matches on something that was already found... */ - /* check_partial_match will put -1 in the data ptr if that is the case */ - test_offset = offset + rm.rm_so; - g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset); - /* make a list of our matches ( tupels)*/ - if (test_offset != -1) { - url_match_t *match = g_slice_new (url_match_t); - match->offset = offset + rm.rm_so; + is_submatch = FALSE; + /* check old matches to see if this has already been matched */ + cursor = match_list; + while (cursor && !is_submatch) { + const url_match_t *old_match = + (const url_match_t *) cursor->data; + guint new_offset = tmp_offset + rm.rm_so; + is_submatch = (new_offset > old_match->offset && + new_offset < old_match->offset + old_match->len); + cursor = g_slist_next (cursor); + } + + if (!is_submatch) { + /* make a list of our matches ( tupels)*/ + match = g_slice_new (url_match_t); + match->offset = tmp_offset + rm.rm_so; match->len = rm.rm_eo - rm.rm_so; match->prefix = patterns[i].prefix; match_list = g_slist_prepend (match_list, match); - } - offset += rm.rm_eo; + } + tmp_offset += rm.rm_eo; } } - for (i = 0; i != pattern_num; ++i) { - regfree (patterns[i].preg); - g_slice_free (regex_t, patterns[i].preg); - } /* don't free patterns itself -- it's static */ + modest_text_utils_hyperlinkify_end (); /* now sort the list, so the matches are in reverse order of occurence. * that way, we can do the replacements starting from the end, so we don't need @@ -835,11 +1164,34 @@ get_url_matches (GString *txt) +/* replace all occurences of needle in haystack with repl*/ +static gchar* +replace_string (const gchar *haystack, const gchar *needle, gchar repl) +{ + gchar *str, *cursor; + + if (!haystack || !needle || strlen(needle) == 0) + return haystack ? g_strdup(haystack) : NULL; + + str = g_strdup (haystack); + + for (cursor = str; cursor && *cursor; ++cursor) { + if (g_str_has_prefix (cursor, needle)) { + cursor[0] = repl; + memmove (cursor + 1, + cursor + strlen (needle), + strlen (cursor + strlen (needle)) + 1); + } + } + + return str; +} + static void -hyperlinkify_plain_text (GString *txt) +hyperlinkify_plain_text (GString *txt, gint offset) { GSList *cursor; - GSList *match_list = get_url_matches (txt); + GSList *match_list = get_url_matches (txt, offset); /* we will work backwards, so the offsets stay valid */ for (cursor = match_list; cursor; cursor = cursor->next) { @@ -848,10 +1200,17 @@ hyperlinkify_plain_text (GString *txt) gchar *url = g_strndup (txt->str + match->offset, match->len); gchar *repl = NULL; /* replacement */ + /* the string still contains $(MARK_AMP_URI_STR)"amp;" for each + * '&' in the original, because of the text->html conversion. + * in the href-URL (and only there), we must convert that back to + * '&' + */ + gchar *href_url = replace_string (url, MARK_AMP_URI_STR "amp;", '&'); + /* the prefix is NULL: use the one that is already there */ repl = g_strdup_printf ("%s", match->prefix ? match->prefix : EMPTY_STRING, - url, url); + href_url, url); /* replace the old thing with our hyperlink * replacement thing */ @@ -860,6 +1219,7 @@ hyperlinkify_plain_text (GString *txt) g_free (url); g_free (repl); + g_free (href_url); g_slice_free (url_match_t, match); } @@ -867,122 +1227,345 @@ hyperlinkify_plain_text (GString *txt) g_slist_free (match_list); } +void +modest_text_utils_hyperlinkify (GString *string_buffer) +{ + gchar *after_body; + gint offset = 0; + + after_body = strstr (string_buffer->str, ""); + if (after_body != NULL) + offset = after_body - string_buffer->str; + hyperlinkify_plain_text (string_buffer, offset); +} -gchar* +/* for optimization reasons, we change the string in-place */ +void modest_text_utils_get_display_address (gchar *address) { - gchar *cursor; + int i; + + g_return_if_fail (address); if (!address) - return NULL; + return; - g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL); + /* should not be needed, and otherwise, we probably won't screw up the address + * more than it already is :) + * g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL); + * */ - g_strchug (address); /* remove leading whitespace */ + /* remove leading whitespace */ + if (address[0] == ' ') + g_strchug (address); + + for (i = 0; address[i]; ++i) { + if (address[i] == '<') { + if (G_UNLIKELY(i == 0)) { + break; /* there's nothing else, leave it */ + }else { + address[i] = '\0'; /* terminate the string here */ + break; + } + } + } - /* from display name */ - cursor = g_strstr_len (address, strlen(address), "<"); - if (cursor == address) /* there's nothing else? leave it */ - return address; - if (cursor) - cursor[0]='\0'; + g_strchomp (address); +} - /* remove (bla bla) from display name */ - cursor = g_strstr_len (address, strlen(address), "("); - if (cursor == address) /* there's nothing else? leave it */ - return address; - if (cursor) - cursor[0]='\0'; - g_strchomp (address); /* remove trailing whitespace */ +gchar * +modest_text_utils_get_display_addresses (const gchar *recipients) +{ + gchar *addresses; + GSList *recipient_list; + + addresses = NULL; + recipient_list = modest_text_utils_split_addresses_list (recipients); + if (recipient_list) { + GString *add_string = g_string_sized_new (strlen (recipients)); + GSList *iter = recipient_list; + gboolean first = TRUE; + + while (iter) { + /* Strings are changed in place */ + modest_text_utils_get_display_address ((gchar *) iter->data); + if (G_UNLIKELY (first)) { + g_string_append_printf (add_string, "%s", (gchar *) iter->data); + first = FALSE; + } else { + g_string_append_printf (add_string, ", %s", (gchar *) iter->data); + } + iter = g_slist_next (iter); + } + g_slist_foreach (recipient_list, (GFunc) g_free, NULL); + g_slist_free (recipient_list); + addresses = g_string_free (add_string, FALSE); + } - return address; + return addresses; } +gchar * +modest_text_utils_get_email_address (const gchar *full_address) +{ + const gchar *left, *right; + + g_return_val_if_fail (full_address, NULL); + + if (!full_address) + return NULL; + + g_return_val_if_fail (g_utf8_validate (full_address, -1, NULL), NULL); + + left = g_strrstr_len (full_address, strlen(full_address), "<"); + if (left == NULL) + return g_strdup (full_address); + + right = g_strstr_len (left, strlen(left), ">"); + if (right == NULL) + return g_strdup (full_address); + + return g_strndup (left + 1, right - left - 1); +} gint modest_text_utils_get_subject_prefix_len (const gchar *sub) { - gint i; - static const gchar* prefix[] = { - "Re:", "RE:", "Fwd:", "FWD:", "FW:", NULL - }; - - if (!sub || (sub[0] != 'R' && sub[0] != 'F')) /* optimization */ - return 0; + gint prefix_len = 0; + + g_return_val_if_fail (sub, 0); - i = 0; + if (!sub) + return 0; - while (prefix[i]) { - if (g_str_has_prefix(sub, prefix[i])) { - int prefix_len = strlen(prefix[i]); - if (sub[prefix_len] == ' ') - ++prefix_len; /* ignore space after prefix as well */ - return prefix_len; - } - ++i; + /* optimization: "Re", "RE", "re","Fwd", "FWD", "fwd","FW","Fw", "fw" */ + if (sub[0] != 'R' && sub[0] != 'F' && sub[0] != 'r' && sub[0] != 'f') + return 0; + else if (sub[0] && sub[1] != 'e' && sub[1] != 'E' && sub[1] != 'w' && sub[1] != 'W') + return 0; + + prefix_len = 2; + if (sub[2] == 'd') + ++prefix_len; + + /* skip over a [...] block */ + if (sub[prefix_len] == '[') { + int c = prefix_len + 1; + while (sub[c] && sub[c] != ']') + ++c; + if (!sub[c]) + return 0; /* no end to the ']' found */ + else + prefix_len = c + 1; } - return 0; + + /* did we find the ':' ? */ + if (sub[prefix_len] == ':') { + ++prefix_len; + if (sub[prefix_len] == ' ') + ++prefix_len; + prefix_len += modest_text_utils_get_subject_prefix_len (sub + prefix_len); +/* g_warning ("['%s','%s']", sub, (char*) sub + prefix_len); */ + return prefix_len; + } else + return 0; } gint modest_text_utils_utf8_strcmp (const gchar* s1, const gchar *s2, gboolean insensitive) { - gint result = 0; - gchar *n1, *n2; - /* work even when s1 and/or s2 == NULL */ +/* work even when s1 and/or s2 == NULL */ if (G_UNLIKELY(s1 == s2)) return 0; - - /* if it's not case sensitive */ - if (!insensitive) - return strcmp (s1 ? s1 : "", s2 ? s2 : ""); - - n1 = g_utf8_collate_key (s1 ? s1 : "", -1); - n2 = g_utf8_collate_key (s2 ? s2 : "", -1); + if (G_UNLIKELY(!s1)) + return -1; + if (G_UNLIKELY(!s2)) + return 1; - result = strcmp (n1, n2); + /* if it's not case sensitive */ + if (!insensitive) { + + /* optimization: shortcut if first char is ascii */ + if (((s1[0] & 0x80)== 0) && ((s2[0] & 0x80) == 0) && + (s1[0] != s2[0])) + return s1[0] - s2[0]; + + return g_utf8_collate (s1, s2); + + } else { + gint result; + gchar *n1, *n2; - g_free (n1); - g_free (n2); + /* optimization: shortcut if first char is ascii */ + if (((s1[0] & 0x80) == 0) && ((s2[0] & 0x80) == 0) && + (tolower(s1[0]) != tolower (s2[0]))) + return tolower(s1[0]) - tolower(s2[0]); + + n1 = g_utf8_strdown (s1, -1); + n2 = g_utf8_strdown (s2, -1); + + result = g_utf8_collate (n1, n2); + + g_free (n1); + g_free (n2); - return result; + return result; + } } -gchar* +const gchar* modest_text_utils_get_display_date (time_t date) { - time_t now; - const guint BUF_SIZE = 64; - gchar date_buf[BUF_SIZE]; - gchar now_buf [BUF_SIZE]; +#define DATE_BUF_SIZE 64 + static gchar date_buf[DATE_BUF_SIZE]; + + /* calculate the # of days since epoch for + * for today and for the date provided + * based on idea from pvanhoof */ + int day = time(NULL) / (24 * 60 * 60); + int date_day = date / (24 * 60 * 60); + + /* if it's today, show the time, if it's not today, show the date instead */ + + /* TODO: take into account the system config for 24/12h */ +#ifdef MODEST_TOOLKIT_HILDON2 + if (day == date_day) /* is the date today? */ + modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, _HL("wdgt_va_24h_time"), date); + else + modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, _HL("wdgt_va_date"), date); +#else + if (day == date_day) /* is the date today? */ + modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%X", date); + else + modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%x", date); +#endif + + return date_buf; /* this is a static buffer, don't free! */ +} + + + +gboolean +modest_text_utils_validate_folder_name (const gchar *folder_name) +{ + /* based on http://msdn2.microsoft.com/en-us/library/aa365247.aspx, + * with some extras */ + + guint len; + gint i; + const gchar **cursor = NULL; + const gchar *forbidden_names[] = { /* windows does not like these */ + "CON", "PRN", "AUX", "NUL", ".", "..", "cur", "tmp", "new", + NULL /* cur, tmp, new are reserved for Maildir */ + }; - now = time (NULL); + /* cannot be NULL */ + if (!folder_name) + return FALSE; - modest_text_utils_strftime (date_buf, BUF_SIZE, "%d/%m/%Y", date); - modest_text_utils_strftime (now_buf, BUF_SIZE, "%d/%m/%Y", now); /* today */ -/* modest_text_utils_strftime (date_buf, BUF_SIZE, "%x", date); */ -/* modest_text_utils_strftime (now_buf, BUF_SIZE, "%x", now); /\* today *\/ */ + /* cannot be empty */ + len = strlen(folder_name); + if (len == 0) + return FALSE; - /* if this is today, get the time instead of the date */ - if (strcmp (date_buf, now_buf) == 0) - modest_text_utils_strftime (date_buf, BUF_SIZE, "%H:%M %P", date); + /* cannot start with a dot, vfat does not seem to like that */ + if (folder_name[0] == '.') + return FALSE; + + /* cannot start or end with a space */ + if (g_ascii_isspace(folder_name[0]) || g_ascii_isspace(folder_name[len - 1])) + return FALSE; + + /* cannot contain a forbidden char */ + for (i = 0; i < len; i++) + if (modest_text_utils_is_forbidden_char (folder_name[i], FOLDER_NAME_FORBIDDEN_CHARS)) + return FALSE; + + /* Cannot contain Windows port numbers. I'd like to use GRegex + but it's still not available in Maemo. sergio */ + if (!g_ascii_strncasecmp (folder_name, "LPT", 3) || + !g_ascii_strncasecmp (folder_name, "COM", 3)) { + glong val; + gchar *endptr; + + /* We skip the first 3 characters for the + comparison */ + val = strtol(folder_name+3, &endptr, 10); + + /* If the conversion to long succeeded then the string + is not valid for us */ + if (*endptr == '\0') + return FALSE; + else + return TRUE; + } - return g_strdup(date_buf); + /* cannot contain a forbidden word */ + if (len <= 4) { + for (cursor = forbidden_names; cursor && *cursor; ++cursor) { + if (g_ascii_strcasecmp (folder_name, *cursor) == 0) + return FALSE; + } + } + + return TRUE; /* it's valid! */ } -gboolean -modest_text_utils_validate_email_address (const gchar *email_address) + + +gboolean +modest_text_utils_validate_domain_name (const gchar *domain) +{ + gboolean valid = FALSE; + regex_t rx; + const gchar* domain_regex = "^([a-z0-9-]*[a-z0-9]\\.)+[a-z0-9-]*[a-z0-9]$"; + + g_return_val_if_fail (domain, FALSE); + + if (!domain) + return FALSE; + + memset (&rx, 0, sizeof(regex_t)); /* coverity wants this... */ + + /* domain name: all alphanum or '-' or '.', + * but beginning/ending in alphanum */ + if (regcomp (&rx, domain_regex, REG_ICASE|REG_EXTENDED|REG_NOSUB)) { + g_warning ("BUG: error in regexp"); + return FALSE; + } + + valid = (regexec (&rx, domain, 1, NULL, 0) == 0); + regfree (&rx); + + return valid; +} + + + +gboolean +modest_text_utils_validate_email_address (const gchar *email_address, + const gchar **invalid_char_position) { int count = 0; const gchar *c = NULL, *domain = NULL; - static gchar *rfc822_specials = "()<>@,;:\\\"[]"; - + static gchar *rfc822_specials = "()<>@,;:\\\"[]&"; + + if (invalid_char_position) + *invalid_char_position = NULL; + + g_return_val_if_fail (email_address, FALSE); + + /* check that the email adress contains exactly one @ */ + if (!strstr(email_address, "@") || + (strstr(email_address, "@") != g_strrstr(email_address, "@"))) + return FALSE; + /* first we validate the name portion (name@domain) */ for (c = email_address; *c; c++) { if (*c == '\"' && @@ -1009,8 +1592,11 @@ modest_text_utils_validate_email_address (const gchar *email_address) break; if (*c <= ' ' || *c >= 127) return FALSE; - if (strchr(rfc822_specials, *c)) + if (strchr(rfc822_specials, *c)) { + if (invalid_char_position) + *invalid_char_position = c; return FALSE; + } } if (c == email_address || *(c - 1) == '.') return FALSE; @@ -1020,28 +1606,37 @@ modest_text_utils_validate_email_address (const gchar *email_address) return FALSE; do { if (*c == '.') { - if (c == domain || *(c - 1) == '.') + if (c == domain || *(c - 1) == '.' || *(c + 1) == '\0') return FALSE; count++; } if (*c <= ' ' || *c >= 127) return FALSE; - if (strchr(rfc822_specials, *c)) + if (strchr(rfc822_specials, *c)) { + if (invalid_char_position) + *invalid_char_position = c; return FALSE; + } } while (*++c); return (count >= 1) ? TRUE : FALSE; } gboolean -modest_text_utils_validate_recipient (const gchar *recipient) +modest_text_utils_validate_recipient (const gchar *recipient, const gchar **invalid_char_position) { gchar *stripped, *current; gchar *right_part; gboolean has_error = FALSE; - if (modest_text_utils_validate_email_address (recipient)) + if (invalid_char_position) + *invalid_char_position = NULL; + + g_return_val_if_fail (recipient, FALSE); + + if (modest_text_utils_validate_email_address (recipient, invalid_char_position)) return TRUE; + stripped = g_strdup (recipient); stripped = g_strstrip (stripped); current = stripped; @@ -1097,7 +1692,7 @@ modest_text_utils_validate_recipient (const gchar *recipient) address = g_strndup (right_part+1, strlen (right_part) - 2); g_free (right_part); - valid = modest_text_utils_validate_email_address (address); + valid = modest_text_utils_validate_email_address (address, invalid_char_position); g_free (address); return valid; } else { @@ -1115,17 +1710,284 @@ modest_text_utils_get_display_size (guint64 size) const guint GB=1024 * MB; if (size == 0) - return g_strdup_printf(_FM("sfil_li_size_kb"), 0); - if (0 < size && size < KB) - return g_strdup_printf (_FM("sfil_li_size_kb"), 1); + return g_strdup_printf (_FM("sfil_li_size_kb"), (int) 0); + if (0 <= size && size < KB) + return g_strdup_printf (_FM("sfil_li_size_1kb_99kb"), (int) 1); else if (KB <= size && size < 100 * KB) - return g_strdup_printf (_FM("sfil_li_size_1kb_99kb"), size / KB); + return g_strdup_printf (_FM("sfil_li_size_1kb_99kb"), (int) size / KB); else if (100*KB <= size && size < MB) - return g_strdup_printf (_FM("sfil_li_size_100kb_1mb"), (float) size / MB); + return g_strdup_printf (_FM("sfil_li_size_100kb_1mb"), (int) size / KB); else if (MB <= size && size < 10*MB) return g_strdup_printf (_FM("sfil_li_size_1mb_10mb"), (float) size / MB); else if (10*MB <= size && size < GB) - return g_strdup_printf (_FM("sfil_li_size_10mb_1gb"), size / MB); + return g_strdup_printf (_FM("sfil_li_size_10mb_1gb"), (float) size / MB); + else + return g_strdup_printf (_FM("sfil_li_size_1gb_or_greater"), (float) size / GB); +} + +static gchar * +get_email_from_address (const gchar * address) +{ + gchar *left_limit, *right_limit; + + left_limit = strstr (address, "<"); + right_limit = g_strrstr (address, ">"); + + if ((left_limit == NULL)||(right_limit == NULL)|| (left_limit > right_limit)) + return g_strdup (address); else - return g_strdup_printf (_FM("sfil_li_size_1gb_or_greater"), (float) size / GB); + return g_strndup (left_limit + 1, (right_limit - left_limit) - 1); +} + +gchar * +modest_text_utils_get_color_string (GdkColor *color) +{ + g_return_val_if_fail (color, NULL); + + return g_strdup_printf ("#%x%x%x%x%x%x%x%x%x%x%x%x", + (color->red >> 12) & 0xf, (color->red >> 8) & 0xf, + (color->red >> 4) & 0xf, (color->red) & 0xf, + (color->green >> 12) & 0xf, (color->green >> 8) & 0xf, + (color->green >> 4) & 0xf, (color->green) & 0xf, + (color->blue >> 12) & 0xf, (color->blue >> 8) & 0xf, + (color->blue >> 4) & 0xf, (color->blue) & 0xf); +} + +gchar * +modest_text_utils_text_buffer_get_text (GtkTextBuffer *buffer) +{ + GtkTextIter start, end; + gchar *slice, *current; + GString *result = g_string_new (""); + + g_return_val_if_fail (buffer && GTK_IS_TEXT_BUFFER (buffer), NULL); + + gtk_text_buffer_get_start_iter (buffer, &start); + gtk_text_buffer_get_end_iter (buffer, &end); + + slice = gtk_text_buffer_get_slice (buffer, &start, &end, FALSE); + current = slice; + + while (current && current != '\0') { + if (g_utf8_get_char (current) == 0xFFFC) { + result = g_string_append_c (result, ' '); + current = g_utf8_next_char (current); + } else { + gchar *next = g_utf8_strchr (current, -1, 0xFFFC); + if (next == NULL) { + result = g_string_append (result, current); + } else { + result = g_string_append_len (result, current, next - current); + } + current = next; + } + } + g_free (slice); + + return g_string_free (result, FALSE); + +} + +gboolean +modest_text_utils_is_forbidden_char (const gchar character, + ModestTextUtilsForbiddenCharType type) +{ + gint i, len; + const gchar *forbidden_chars = NULL; + + /* We need to get the length in the switch because the + compiler needs to know the size at compile time */ + switch (type) { + case ACCOUNT_TITLE_FORBIDDEN_CHARS: + forbidden_chars = account_title_forbidden_chars; + len = G_N_ELEMENTS (account_title_forbidden_chars); + break; + case FOLDER_NAME_FORBIDDEN_CHARS: + forbidden_chars = folder_name_forbidden_chars; + len = G_N_ELEMENTS (folder_name_forbidden_chars); + break; + case USER_NAME_FORBIDDEN_NAMES: + forbidden_chars = user_name_forbidden_chars; + len = G_N_ELEMENTS (user_name_forbidden_chars); + break; + default: + g_return_val_if_reached (TRUE); + } + + for (i = 0; i < len ; i++) + if (forbidden_chars[i] == character) + return TRUE; + + return FALSE; /* it's valid! */ +} + +gchar * +modest_text_utils_label_get_selection (GtkLabel *label) +{ + gint start, end; + gchar *selection; + + if (gtk_label_get_selection_bounds (GTK_LABEL (label), &start, &end)) { + const gchar *start_offset; + const gchar *end_offset; + start_offset = gtk_label_get_text (GTK_LABEL (label)); + start_offset = g_utf8_offset_to_pointer (start_offset, start); + end_offset = gtk_label_get_text (GTK_LABEL (label)); + end_offset = g_utf8_offset_to_pointer (end_offset, end); + selection = g_strndup (start_offset, end_offset - start_offset); + return selection; + } else { + return g_strdup (""); + } +} + +static gboolean +_forward_search_image_char (gunichar ch, + gpointer userdata) +{ + return (ch == 0xFFFC); +} + +gboolean +modest_text_utils_buffer_selection_is_valid (GtkTextBuffer *buffer) +{ + gboolean result; + GtkTextIter start, end; + + g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE); + + result = gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (buffer)); + + /* check there are no images in selection */ + if (result) { + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + if (gtk_text_iter_get_char (&start)== 0xFFFC) + result = FALSE; + else { + gtk_text_iter_backward_char (&end); + if (gtk_text_iter_forward_find_char (&start, _forward_search_image_char, + NULL, &end)) + result = FALSE; + } + + } + + return result; +} + +static void +remove_quotes (gchar **quotes) +{ + if (g_str_has_prefix (*quotes, "\"") && g_str_has_suffix (*quotes, "\"")) { + gchar *result; + result = g_strndup ((*quotes)+1, strlen (*quotes) - 2); + g_free (*quotes); + *quotes = result; + } +} + +gchar * +modest_text_utils_escape_mnemonics (const gchar *text) +{ + const gchar *p; + GString *result = NULL; + + if (text == NULL) + return NULL; + + result = g_string_new (""); + for (p = text; *p != '\0'; p++) { + if (*p == '_') + result = g_string_append (result, "__"); + else + result = g_string_append_c (result, *p); + } + + return g_string_free (result, FALSE); +} + +gchar * +modest_text_utils_simplify_recipients (const gchar *recipients) +{ + GSList *addresses, *node; + GString *result; + gboolean is_first = TRUE; + + if (recipients == NULL) + return g_strdup (""); + + addresses = modest_text_utils_split_addresses_list (recipients); + result = g_string_new (""); + + for (node = addresses; node != NULL; node = g_slist_next (node)) { + const gchar *address = (const gchar *) node->data; + gchar *left_limit, *right_limit; + left_limit = strstr (address, "<"); + right_limit = g_strrstr (address, ">"); + + if (is_first) + is_first = FALSE; + else + result = g_string_append (result, ", "); + + if ((left_limit == NULL)||(right_limit == NULL)|| (left_limit > right_limit)) { + result = g_string_append (result, address); + } else { + gchar *name_side; + gchar *email_side; + name_side = g_strndup (address, left_limit - address); + name_side = g_strstrip (name_side); + remove_quotes (&name_side); + email_side = get_email_from_address (address); + if (name_side && email_side && !strcmp (name_side, email_side)) { + result = g_string_append (result, email_side); + } else { + result = g_string_append (result, address); + } + g_free (name_side); + g_free (email_side); + } + + } + g_slist_foreach (addresses, (GFunc)g_free, NULL); + g_slist_free (addresses); + + return g_string_free (result, FALSE); + +} + +GSList * +modest_text_utils_remove_duplicate_addresses_list (GSList *address_list) +{ + GSList *new_list, *iter; + GHashTable *table; + + g_return_val_if_fail (address_list, NULL); + + table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + new_list = address_list; + iter = address_list; + while (iter) { + const gchar* address = (const gchar*)iter->data; + + /* We need only the email to just compare it and not + the full address which would make "a " + different from "a@a.com" */ + const gchar *email = get_email_from_address (address); + + /* ignore the address if already seen */ + if (g_hash_table_lookup (table, email) == 0) { + g_hash_table_insert (table, (gchar*)email, GINT_TO_POINTER(1)); + iter = g_slist_next (iter); + } else { + GSList *tmp = g_slist_next (iter); + new_list = g_slist_delete_link (new_list, iter); + iter = tmp; + } + } + + g_hash_table_unref (table); + + return new_list; }
%s%s
%s%s
%s%s
%s%s