X-Git-Url: http://git.maemo.org/git/?p=modest;a=blobdiff_plain;f=src%2Fmodest-text-utils.c;h=59f5cac3c0121ef33896849f6bf78c17503d1717;hp=7d2806ee369f8790faf8ed291243068feed99f33;hb=0fbbf474b218d00f517fae83561a05f6919679e7;hpb=a80b3deaaeee42e48cc3c349f377cae657bfd543 diff --git a/src/modest-text-utils.c b/src/modest-text-utils.c index 7d2806e..59f5cac 100644 --- a/src/modest-text-utils.c +++ b/src/modest-text-utils.c @@ -42,7 +42,7 @@ #include #include #include - +#include #ifdef HAVE_CONFIG_H #include @@ -63,6 +63,8 @@ * will hang modest */ #define HYPERLINKIFY_MAX_LENGTH (1024*50) +#define SIGNATURE_MARKER "--" + /* * we need these regexps to find URLs in plain text e-mails @@ -81,14 +83,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_$%&=?/~#]",\ + { "(file|rtsp|http|ftp|https|mms|mmsh|webcal|feed|rtsp|rdp|lastfm)://[-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_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\ + { "www\\.[-a-z0-9_$.+!*(),;:@%=?/~#" MARK_AMP_URI_STR "]+[-a-z0-9_$%" MARK_AMP_URI_STR "=?/~#]",\ NULL, "http://" }, \ - { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\ + { "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},\ @@ -100,7 +128,7 @@ const gchar account_title_forbidden_chars[] = { '\\', '/', ':', '*', '?', '\'', '<', '>', '|', '^' }; const gchar folder_name_forbidden_chars[] = { - '<', '>', ':', '\'', '/', '\\', '|', '?', '*', '^', '%', '$' + '<', '>', ':', '\'', '/', '\\', '|', '?', '*', '^', '%', '$', '#', '&' }; const gchar user_name_forbidden_chars[] = { '<', '>' @@ -111,14 +139,15 @@ const guint USER_NAME_FORBIDDEN_CHARS_LENGTH = G_N_ELEMENTS (user_name_forbidden /* 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 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); @@ -184,14 +213,17 @@ modest_text_utils_cite (const gchar *text, g_return_val_if_fail (text, NULL); g_return_val_if_fail (content_type, NULL); - if (!signature) - retval = g_strdup (""); - else if (strcmp(content_type, "text/html") == 0) { - tmp_sig = g_strconcat ("\n", signature, NULL); - retval = modest_text_utils_convert_to_html_body(tmp_sig, -1, TRUE); + if (!signature) { + tmp_sig = g_strdup (text); + } else { + tmp_sig = g_strconcat (text, "\n", SIGNATURE_MARKER, "\n", signature, NULL); + } + + 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 = g_strconcat (text, "\n", signature, NULL); + retval = tmp_sig; } return retval; @@ -203,6 +235,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:"", @@ -248,6 +282,11 @@ 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'; + /* does not work on old maemo glib: * g_date_set_time_t (&date, timet); */ @@ -258,32 +297,44 @@ modest_text_utils_strftime(char *s, gsize max, const char *fmt, time_t timet) 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 || subject[0] == '\0') subject = _("mail_va_no_subject"); - tmp = g_strchug (g_strdup (subject)); + subject_dup = g_strdup (subject); + tmp = g_strchug (subject_dup); - if (!strncmp (tmp, prefix, strlen (prefix))) { - return tmp; - } else { - g_free (tmp); - return g_strdup_printf ("%s %s", prefix, subject); - } + /* 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); @@ -320,10 +371,59 @@ modest_text_utils_remove_address (const gchar *address_list, const gchar *addres 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); + + return new_list; +} + + static void -modest_text_utils_convert_buffer_to_html (GString *html, const gchar *data, gssize n) +modest_text_utils_convert_buffer_to_html_start (GString *html, const gchar *data, gssize n) { - guint i; + guint i; gboolean space_seen = FALSE; guint break_dist = 0; /* distance since last break point */ @@ -332,7 +432,7 @@ modest_text_utils_convert_buffer_to_html (GString *html, const gchar *data, gssi /* 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, ' '); @@ -342,23 +442,32 @@ modest_text_utils_convert_buffer_to_html (GString *html, const gchar *data, gssi /* 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: 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 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, "    "); break_dist=0; break; /* note the space at the end*/ + 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 */ @@ -373,11 +482,25 @@ modest_text_utils_convert_buffer_to_html (GString *html, const gchar *data, gssi } } + +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; @@ -391,13 +514,15 @@ modest_text_utils_convert_to_html (const gchar *data) "" ""); - modest_text_utils_convert_buffer_to_html (html, data, -1); + modest_text_utils_convert_buffer_to_html_start (html, data, -1); g_string_append (html, ""); if (len <= HYPERLINKIFY_MAX_LENGTH) - hyperlinkify_plain_text (html); + hyperlinkify_plain_text (html, 0); + modest_text_utils_convert_buffer_to_html_finish (html); + return g_string_free (html, FALSE); } @@ -405,7 +530,9 @@ gchar * modest_text_utils_convert_to_html_body (const gchar *data, gssize n, gboolean hyperlinkify) { GString *html; - + + g_return_val_if_fail (data, NULL); + if (!data) return NULL; @@ -413,11 +540,13 @@ modest_text_utils_convert_to_html_body (const gchar *data, gssize n, gboolean hy n = strlen (data); html = g_string_sized_new (1.5 * n); /* just a guess... */ - modest_text_utils_convert_buffer_to_html (html, data, n); + modest_text_utils_convert_buffer_to_html_start (html, data, n); if (hyperlinkify && (n < HYPERLINKIFY_MAX_LENGTH)) - hyperlinkify_plain_text (html); + hyperlinkify_plain_text (html, 0); + modest_text_utils_convert_buffer_to_html_finish (html); + return g_string_free (html, FALSE); } @@ -479,49 +608,55 @@ 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, guint position, @@ -563,6 +698,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 ************************ */ @@ -615,15 +774,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; @@ -634,15 +795,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 { @@ -731,32 +896,25 @@ quoted_attachments (GList *attachments) } -static gchar * -modest_text_utils_quote_plain_text (const gchar *text, - const gchar *cite, - const gchar *signature, - GList *attachments, - int limit) +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; - gchar *attachments_string = NULL; - - /* remaining will store the rest of the line if we have to break it */ - q = g_string_new ("\n"); - q = g_string_append (q, cite); - q = g_string_append_c (q, '\n'); - 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) { @@ -768,7 +926,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); @@ -786,22 +944,54 @@ 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])); - attachments_string = quoted_attachments (attachments); - q = g_string_append (q, attachments_string); - g_free (attachments_string); + 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) { - q = g_string_append_c (q, '\n'); + q = g_string_append (q, "\n--\n"); 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, @@ -809,38 +999,37 @@ modest_text_utils_quote_html (const gchar *text, GList *attachments, int limit) { - gchar *result = NULL; - gchar *signature_result = NULL; - const gchar *format = \ - "\n" \ - "\n" \ - "\n" \ - "
%s
" \ - "
%s
%s
%s
\n" \ - "\n" \ - "\n"; - gchar *attachments_string = NULL; - gchar *q_attachments_string = NULL; - gchar *q_cite = NULL; - gchar *html_text = NULL; - - if (signature == NULL) - signature_result = g_strdup (""); - else - signature_result = modest_text_utils_convert_to_html_body (signature, -1, TRUE); + 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, 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, ">", 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, ""); - attachments_string = quoted_attachments (attachments); - q_attachments_string = modest_text_utils_convert_to_html_body (attachments_string, -1, TRUE); - q_cite = modest_text_utils_convert_to_html_body (cite, -1, TRUE); - html_text = modest_text_utils_convert_to_html_body (text, -1, TRUE); - result = g_strdup_printf (format, signature_result, q_cite, html_text, q_attachments_string); - g_free (q_cite); - g_free (html_text); - g_free (attachments_string); - g_free (q_attachments_string); - g_free (signature_result); - - return result; + return g_string_free (result_string, FALSE); } static gint @@ -849,7 +1038,7 @@ cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2) return match2->offset - match1->offset; } -static gboolean url_matches_block = 0; +static gint url_matches_block = 0; static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS; @@ -862,8 +1051,11 @@ compile_patterns () patterns[i].preg = g_slice_new0 (regex_t); /* this should not happen */ - g_return_val_if_fail (regcomp (patterns[i].preg, patterns[i].regex, - REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0, FALSE); + 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; } @@ -897,10 +1089,10 @@ modest_text_utils_hyperlinkify_end (void) 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; const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t); @@ -910,13 +1102,13 @@ get_url_matches (GString *txt) /* find all the matches */ for (i = 0; i != pattern_num; ++i) { - offset = 0; + tmp_offset = offset; while (1) { url_match_t *match; gboolean is_submatch; GSList *cursor; - if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) { + 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 */ } @@ -929,7 +1121,7 @@ get_url_matches (GString *txt) while (cursor && !is_submatch) { const url_match_t *old_match = (const url_match_t *) cursor->data; - guint new_offset = offset + rm.rm_so; + 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); @@ -938,13 +1130,12 @@ get_url_matches (GString *txt) if (!is_submatch) { /* make a list of our matches ( tupels)*/ match = g_slice_new (url_match_t); - match->offset = offset + rm.rm_so; + 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; } } @@ -961,11 +1152,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) { @@ -974,10 +1188,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 */ @@ -986,6 +1207,7 @@ hyperlinkify_plain_text (GString *txt) g_free (url); g_free (repl); + g_free (href_url); g_slice_free (url_match_t, match); } @@ -993,12 +1215,26 @@ 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); +} + /* for optimization reasons, we change the string in-place */ void modest_text_utils_get_display_address (gchar *address) { int i; + + g_return_if_fail (address); if (!address) return; @@ -1014,14 +1250,16 @@ modest_text_utils_get_display_address (gchar *address) for (i = 0; address[i]; ++i) { if (address[i] == '<') { - if (G_UNLIKELY(i == 0)) - return; /* there's nothing else, leave it */ - else { + if (G_UNLIKELY(i == 0)) { + break; /* there's nothing else, leave it */ + }else { address[i] = '\0'; /* terminate the string here */ - return; + break; } } } + + g_strchomp (address); } @@ -1032,6 +1270,8 @@ 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; @@ -1052,86 +1292,112 @@ modest_text_utils_get_email_address (const gchar *full_address) gint modest_text_utils_get_subject_prefix_len (const gchar *sub) { - gint i; - static const gchar* prefix[] = { - "Re:", "RE:", "RV:", "re:" - "Fwd:", "FWD:", "FW:", "fwd:", "Fw:", "fw:", NULL - }; - - if (!sub || (sub[0] != 'R' && sub[0] != 'F' && sub[0] != 'r' && sub[0] != 'f')) /* optimization */ - return 0; + gint prefix_len = 0; - i = 0; + g_return_val_if_fail (sub, 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 : ""); + if (G_UNLIKELY(!s1)) + return -1; + if (G_UNLIKELY(!s2)) + return 1; - n1 = g_utf8_collate_key (s1 ? s1 : "", -1); - n2 = g_utf8_collate_key (s2 ? s2 : "", -1); - - result = strcmp (n1, n2); + /* if it's not case sensitive */ + if (!insensitive) { - g_free (n1); - g_free (n2); + /* 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; + + /* 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; + } } const gchar* modest_text_utils_get_display_date (time_t date) { - time_t now; #define DATE_BUF_SIZE 64 - static const guint ONE_DAY = 24 * 60 * 60; /* seconds in one day */ 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); - gchar today_buf [DATE_BUF_SIZE]; - - modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%x", date); - - now = time (NULL); + /* if it's today, show the time, if it's not today, show the date instead */ - /* we check if the date is within the last 24h, if not, we don't - * have to do the extra, expensive strftime, which was very visible - * in the profiles. - */ - if (abs(now - date) < ONE_DAY) { - - /* it's within the last 24 hours, but double check */ - /* use the localized dates */ - modest_text_utils_strftime (today_buf, DATE_BUF_SIZE, "%x", now); + /* TODO: take into account the system config for 24/12h */ + 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); - /* if it's today, use the time instead */ - if (strcmp (date_buf, today_buf) == 0) - modest_text_utils_strftime (date_buf, DATE_BUF_SIZE, "%X", date); - } - - return date_buf; + return date_buf; /* this is a static buffer, don't free! */ } @@ -1146,9 +1412,8 @@ modest_text_utils_validate_folder_name (const gchar *folder_name) gint i; const gchar **cursor = NULL; const gchar *forbidden_names[] = { /* windows does not like these */ - "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", - "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", - ".", "..", NULL + "CON", "PRN", "AUX", "NUL", ".", "..", "cur", "tmp", "new", + NULL /* cur, tmp, new are reserved for Maildir */ }; /* cannot be NULL */ @@ -1160,6 +1425,10 @@ modest_text_utils_validate_folder_name (const gchar *folder_name) if (len == 0) return FALSE; + /* 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; @@ -1168,6 +1437,25 @@ modest_text_utils_validate_folder_name (const gchar *folder_name) 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; + } /* cannot contain a forbidden word */ if (len <= 4) { @@ -1176,6 +1464,7 @@ modest_text_utils_validate_folder_name (const gchar *folder_name) return FALSE; } } + return TRUE; /* it's valid! */ } @@ -1186,13 +1475,15 @@ 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]$"; + const gchar* domain_regex = "^([a-z0-9-]*[a-z0-9]\\.)+[a-z0-9-]*[a-z0-9]$"; - memset (&rx, 0, sizeof(regex_t)); /* coverity wants this... */ + 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)) { @@ -1209,21 +1500,22 @@ modest_text_utils_validate_domain_name (const gchar *domain) gboolean -modest_text_utils_validate_email_address (const gchar *email_address, const gchar **invalid_char_position) +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 = "()<>@,;:\\\"[]&"; - - if (invalid_char_position != NULL) + + 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, "@"))) - { + (strstr(email_address, "@") != g_strrstr(email_address, "@"))) return FALSE; - } /* first we validate the name portion (name@domain) */ for (c = email_address; *c; c++) { @@ -1288,8 +1580,14 @@ modest_text_utils_validate_recipient (const gchar *recipient, const gchar **inva gchar *right_part; gboolean has_error = FALSE; + 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; @@ -1363,19 +1661,19 @@ 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); 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"), (int) size / MB); else - return g_strdup_printf (_FM("sfil_li_size_1gb_or_greater"), (float) size / GB); + return g_strdup_printf (_FM("sfil_li_size_1gb_or_greater"), (float) size / GB); } static gchar * @@ -1392,9 +1690,10 @@ get_email_from_address (const gchar * address) return g_strndup (left_limit + 1, (right_limit - left_limit) - 1); } -gchar * +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, @@ -1412,8 +1711,8 @@ modest_text_utils_text_buffer_get_text (GtkTextBuffer *buffer) gchar *slice, *current; GString *result = g_string_new (""); - g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL); - + 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); @@ -1472,3 +1771,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); +}