Fix for bug NB#81584.
[modest] / src / modest-text-utils.c
index 3a89582..5906bb7 100644 (file)
 #define HYPERLINKIFY_MAX_LENGTH (1024*50)
 
 
-/*
- * 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
- */
-#define MARK_AMP '\007'
-#define MARK_AMP_STR "\007"
 
 /*
  * we need these regexps to find URLs in plain text e-mails
@@ -90,19 +83,44 @@ struct _url_match_t {
        const gchar* prefix;
 };
 
-/* note: match MARK_AMP_STR as well, because after txt->html, a '&' will look like $(MARK_AMP_STR)"amp;" */
+
+/*
+ * 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 &amp; 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 '&amp;'
+ * 
+ * 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-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_STR "]+[-a-z0-9_$%" MARK_AMP_STR "=?/~#]",\
+       { "(file|rtsp|http|ftp|https|mms|mmsh|rtsp|rdp|lastfm)://[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR \
+                       "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",      \
          NULL, NULL },\
-       { "^www\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_STR "]+[-a-z0-9_$%" MARK_AMP_STR "=?/~#]",\
+       { "www\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",\
                        NULL, "http://" },                              \
-       { "^ftp\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_STR "]+[-a-z0-9_$%" MARK_AMP_STR "=?/~#]",\
+       { "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|xmpp):[-_a-z@0-9.+]+", \
           NULL, NULL},                                             \
-       { "^mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+",                   \
+       { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+",                    \
          NULL, NULL},\
-       { "^[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\
+       { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\
          NULL, "mailto:"}\
        }
 
@@ -213,6 +231,8 @@ forward_cite (const gchar *from,
              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:"",
@@ -380,7 +400,7 @@ modest_text_utils_convert_buffer_to_html_start (GString *html, const gchar *data
 
        /* replace with special html chars where needed*/
        for (i = 0; i != n; ++i)  {
-               char kar = data[i];
+               guchar kar = data[i];
                
                if (space_seen && kar != ' ') {
                        g_string_append_c (html, ' ');
@@ -390,21 +410,25 @@ modest_text_utils_convert_buffer_to_html_start (GString *html, const gchar *data
                /* 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) {
+               if (++break_dist >= 256 && kar < 127) {
                        g_string_append_c (html, '\n');
                        break_dist = 0;
                }
                
                switch (kar) {
                case 0:
-               case MARK_AMP: /* this is a temp place holder for '&'; we can only
+               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_STR "amp;");  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 &apos; --> wpeditor will try to re-convert it... */    
@@ -433,7 +457,7 @@ 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)
+               if ((html->str)[i] == MARK_AMP || (html->str)[i] == MARK_AMP_URI)
                        (html->str)[i] = '&';
 }
 
@@ -552,53 +576,6 @@ modest_text_utils_get_addresses_indexes (const gchar *addresses, GSList **start_
        return;
 }
 
-#if 0
-GSList *
-modest_text_utils_split_addresses_list (const gchar *addresses)
-{
-       gchar *current, *start, *last_blank;
-       GSList *result = NULL;
-
-       start = (gchar *) addresses;
-       current = start;
-       last_blank = start;
-
-       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);
-               }
-                               
-               current = g_utf8_next_char (current);
-       }
-
-       if (start != current) {
-               gchar *new_address = NULL;
-               new_address = g_strndup (start, current - last_blank);
-               result = g_slist_prepend (result, new_address);
-       }
-
-       result = g_slist_reverse (result);
-       return result;
-
-}
-#endif
-
-
-
 
 GSList *
 modest_text_utils_split_addresses_list (const gchar *addresses)
@@ -607,6 +584,8 @@ modest_text_utils_split_addresses_list (const gchar *addresses)
        const gchar *my_addrs = addresses;
        const gchar *end;
        gchar *addr;
+
+       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] == ';'))
@@ -674,6 +653,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 ************************ */
@@ -855,10 +858,15 @@ modest_text_utils_quote_plain_text (const gchar *text,
        gsize len;
        gchar *attachments_string = NULL;
 
-       /* remaining will store the rest of the line if we have to break it */
        q = g_string_new ("\n");
+       if (signature != NULL) {
+               q = g_string_append (q, signature);
+               q = g_string_append_c (q, '\n');
+       }
        q = g_string_append (q, cite);
        q = g_string_append_c (q, '\n');
+
+       /* remaining will store the rest of the line if we have to break it */
        remaining = g_string_new ("");
 
        iter = text;
@@ -905,11 +913,6 @@ modest_text_utils_quote_plain_text (const gchar *text,
        q = g_string_append (q, attachments_string);
        g_free (attachments_string);
 
-       if (signature != NULL) {
-               q = g_string_append_c (q, '\n');
-               q = g_string_append (q, signature);
-       }
-
        return g_string_free (q, FALSE);
 }
 
@@ -1110,12 +1113,12 @@ 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_STR)"amp;" for each
+               /* 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_STR "amp;", '&');
+               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 ("<a href=\"%s%s\">%s</a>",
@@ -1256,7 +1259,7 @@ modest_text_utils_utf8_strcmp (const gchar* s1, const gchar *s2, gboolean insens
        /* if it's not case sensitive */
        if (!insensitive) {
 
-               /* optimization: short cut if first char is ascii */ 
+               /* optimization: shortcut if first char is ascii */ 
                if (((s1[0] & 0xf0)== 0) && ((s2[0] & 0xf0) == 0)) 
                        return s1[0] - s2[0];
                
@@ -1654,3 +1657,77 @@ modest_text_utils_is_forbidden_char (const gchar character,
 
        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;
+}
+
+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);
+}