Take into account unichar space on converting to plain text quoted text
[modest] / src / modest-text-utils.c
index 4203b03..99cb710 100644 (file)
@@ -63,8 +63,6 @@
  * will hang modest
  */
 #define HYPERLINKIFY_MAX_LENGTH (1024*50)
-#define SIGNATURE_MARKER "--"
-
 
 /*
  * we need these regexps to find URLs in plain text e-mails
@@ -109,8 +107,8 @@ struct _url_match_t {
 
 /* 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|mms|mmsh|webcal|feed|rtsp|rdp|lastfm|sip)://[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR \
-                       "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",      \
+       { "(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_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",\
                        NULL, "http://" },                              \
@@ -216,7 +214,7 @@ modest_text_utils_cite (const gchar *text,
        if (!signature) {
                tmp_sig = g_strdup (text);
        } else {
-               tmp_sig = g_strconcat (text, "\n", SIGNATURE_MARKER, "\n", signature, NULL);
+               tmp_sig = g_strconcat (text, "\n", MODEST_TEXT_UTILS_SIGNATURE_MARKER, "\n", signature, NULL);
        }
 
        if (strcmp (content_type, "text/html") == 0) {
@@ -286,6 +284,8 @@ modest_text_utils_strftime(char *s, gsize max, const char *fmt, time_t timet)
           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);
@@ -316,7 +316,23 @@ modest_text_utils_derived_subject (const gchar *subject, const gchar *prefix)
                        tmp += prefix_len;
                        tmp = g_strchug (tmp);
                } else {
-                       break;
+                       gchar *prefix_down, *tmp_down;
+
+                       /* We need this to properly check the cases of
+                          some clients adding FW: instead of Fw: for
+                          example */
+                       prefix_down = g_utf8_strdown (prefix, -1);
+                       tmp_down = g_utf8_strdown (tmp, -1);
+                       if (g_str_has_prefix (tmp_down, prefix_down)) {
+                               tmp += prefix_len;
+                               tmp = g_strchug (tmp);
+                               g_free (prefix_down);
+                               g_free (tmp_down);
+                       } else {
+                               g_free (prefix_down);
+                               g_free (tmp_down);
+                               break;
+                       }
                }
        } while (tmp);
 
@@ -326,6 +342,39 @@ modest_text_utils_derived_subject (const gchar *subject, const gchar *prefix)
        return retval;
 }
 
+
+/* Performs a case-insensitive strstr for ASCII strings */
+static const gchar *
+ascii_stristr(const gchar *haystack, const gchar *needle)
+{
+       int needle_len;
+       int haystack_len;
+       const gchar *pos;
+       const gchar *max_pos;
+
+       if (haystack == NULL || needle == NULL) {
+               return haystack;  /* as in strstr */
+       }
+
+       needle_len = strlen(needle);
+
+       if (needle_len == 0) {
+               return haystack;  /* as in strstr */
+       }
+
+       haystack_len = strlen(haystack);
+       max_pos = haystack + haystack_len - needle_len;
+
+       for (pos = haystack; pos <= max_pos; pos++) {
+               if (g_ascii_strncasecmp (pos, needle, needle_len) == 0) {
+                       return pos;
+               }
+       }
+
+       return NULL;
+}
+
+
 gchar*
 modest_text_utils_remove_address (const gchar *address_list, const gchar *address)
 {
@@ -334,26 +383,26 @@ modest_text_utils_remove_address (const gchar *address_list, const gchar *addres
        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 *) email_address)) {
+       if (!ascii_stristr ((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);
-       
+
        token = strtok_r (dup, ",", &ptr);
 
        while (token != NULL) {
                /* Add to list if not found */
-               if (!strstr ((const char *) token, (const char *) email_address)) {
+               if (!ascii_stristr ((const char *) token, (const char *) email_address)) {
                        if (filtered_emails->len == 0)
                                g_string_append_printf (filtered_emails, "%s", g_strstrip (token));
                        else
@@ -416,6 +465,9 @@ modest_text_utils_remove_duplicate_addresses (const gchar *address_list)
        g_slist_foreach (addresses, (GFunc)g_free, NULL);
        g_slist_free (addresses);
 
+       if (new_list == NULL)
+               new_list = g_strdup ("");
+
        return new_list;
 }
 
@@ -435,7 +487,7 @@ modest_text_utils_convert_buffer_to_html_start (GString *html, const gchar *data
                guchar kar = data[i];
                
                if (space_seen && kar != ' ') {
-                       g_string_append_c (html, ' ');
+                       g_string_append (html, " ");
                        space_seen = FALSE;
                }
                
@@ -472,7 +524,6 @@ modest_text_utils_convert_buffer_to_html_start (GString *html, const gchar *data
                        break_dist = 0;
                        if (space_seen) { /* second space in a row */
                                g_string_append (html, "&nbsp; ");
-                               space_seen = FALSE;
                        } else
                                space_seen = TRUE;
                        break;
@@ -766,7 +817,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;
@@ -922,6 +973,7 @@ modest_text_utils_quote_body (GString *output, const gchar *text,
                                g_string_prepend (l, remaining->str);
                        } else {
                                do {
+                                       gunichar remaining_first;
                                        breakpoint =
                                                get_breakpoint (remaining->str,
                                                                rem_indent,
@@ -930,9 +982,11 @@ modest_text_utils_quote_body (GString *output, const gchar *text,
                                                       remaining, breakpoint);
                                        g_string_erase (remaining, 0,
                                                        breakpoint);
-                                       if (remaining->str[0] == ' ') {
-                                               g_string_erase (remaining, 0,
-                                                               1);
+                                       remaining_first = g_utf8_get_char_validated (remaining->str, remaining->len);
+                                       if (remaining_first != ((gunichar) -1)) {
+                                               if (g_unichar_isspace (remaining_first)) {
+                                                       g_string_erase (remaining, 0, g_utf8_next_char (remaining->str) - remaining->str);
+                                               }
                                        }
                                } while (remaining->len);
                        }
@@ -964,7 +1018,7 @@ modest_text_utils_quote_plain_text (const gchar *text,
        q = g_string_new ("");
 
        if (signature != NULL) {
-               q = g_string_append (q, "\n--\n");
+               g_string_append_printf (q, "\n%s\n", MODEST_TEXT_UTILS_SIGNATURE_MARKER);
                q = g_string_append (q, signature);
        }
 
@@ -1011,7 +1065,7 @@ modest_text_utils_quote_html (const gchar *text,
                GString *quoted_text;
                g_string_append (result_string, "<pre>\n");
                if (signature) {
-                       quote_html_add_to_gstring (result_string, SIGNATURE_MARKER);
+                       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);
@@ -1040,6 +1094,7 @@ cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
 
 static gint url_matches_block = 0;
 static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
+static GMutex *url_patterns_mutex = NULL;
 
 
 static gboolean
@@ -1074,17 +1129,25 @@ free_patterns ()
 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)
 {
+       g_mutex_lock (url_patterns_mutex);
        url_matches_block--;
        if (url_matches_block <= 0)
                free_patterns ();
+       g_mutex_unlock (url_patterns_mutex);
 }
 
 
@@ -1263,7 +1326,37 @@ modest_text_utils_get_display_address (gchar *address)
 }
 
 
+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 addresses;
+}
 
 
 gchar *
@@ -1392,10 +1485,17 @@ modest_text_utils_get_display_date (time_t date)
        /* 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! */
 }
@@ -1667,11 +1767,11 @@ modest_text_utils_get_display_size (guint64 size)
        else if (KB <= size && size < 100 * 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"), (int) 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);
 }
@@ -1826,6 +1926,17 @@ modest_text_utils_buffer_selection_is_valid (GtkTextBuffer *buffer)
        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)
 {
@@ -1845,3 +1956,125 @@ modest_text_utils_escape_mnemonics (const gchar *text)
        
        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 <a@a.com>"
+                  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;
+}
+
+gchar *
+modest_text_utils_get_secure_header (const gchar *value,
+                                    const gchar *header)
+{
+       const gint max_len = 128;
+       gchar *new_value = NULL;
+       gchar *needle = g_strrstr (value, header);
+
+       if (needle && value != needle)
+               new_value = g_strdup (needle + strlen (header));
+
+       if (!new_value)
+               new_value = g_strdup (value);
+
+       /* Do a max length check to prevent DoS attacks caused by huge
+          malformed headers */
+       if (g_utf8_validate (new_value, -1, NULL)) {
+               if (g_utf8_strlen (new_value, -1) > max_len) {
+                       gchar *tmp = g_malloc0 (max_len * 4);
+                       g_utf8_strncpy (tmp, (const gchar *) new_value, max_len);
+                       g_free (new_value);
+                       new_value = tmp;
+               }
+       } else {
+               if (strlen (new_value) > max_len) {
+                       gchar *tmp = g_malloc0 (max_len);
+                       strncpy (new_value, tmp, max_len);
+                       g_free (new_value);
+                       new_value = tmp;
+               }
+       }
+
+       return new_value;
+}