+static gchar* cite (const time_t sent_date, const gchar *from);
+static void hyperlinkify_plain_text (GString *txt);
+static gint cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2);
+static GSList* get_url_matches (GString *txt);
+
+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,
+ 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);
+static int get_breakpoint (const gchar * s, const gint indent, const gint limit);
+
+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 *signature,
+ GList *attachments,
+ int limit);
+static gchar* get_email_from_address (const gchar *address);
+
+
+/* ******************************************************************* */
+/* ************************* PUBLIC FUNCTIONS ************************ */
+/* ******************************************************************* */
+
+gchar *
+modest_text_utils_quote (const gchar *text,
+ const gchar *content_type,
+ const gchar *signature,
+ const gchar *from,
+ const time_t sent_date,
+ GList *attachments,
+ int limit)
+{
+ gchar *retval, *cited;
+
+ g_return_val_if_fail (text, NULL);
+ g_return_val_if_fail (content_type, NULL);
+
+ cited = cite (sent_date, from);
+
+ if (content_type && strcmp (content_type, "text/html") == 0)
+ /* TODO: extract the <body> of the HTML and pass it to
+ the function */
+ retval = modest_text_utils_quote_html (text, cited, signature, attachments, limit);
+ else
+ retval = modest_text_utils_quote_plain_text (text, cited, signature, attachments, limit);
+
+ g_free (cited);
+
+ return retval;
+}
+
+
+gchar *
+modest_text_utils_cite (const gchar *text,
+ const gchar *content_type,
+ const gchar *signature,
+ const gchar *from,
+ time_t sent_date)
+{
+ gchar *retval;
+ gchar *tmp_sig;
+
+ 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);
+ g_free (tmp_sig);
+ } else {
+ retval = g_strconcat (text, "\n", signature, NULL);
+ }
+
+ 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,
+ const gchar *signature,
+ const gchar *from,
+ time_t sent_date,
+ const gchar *to,
+ const gchar *subject)
+{
+ gchar sent_str[101];
+ gchar *cited;
+ gchar *retval;
+
+ g_return_val_if_fail (text, NULL);
+ g_return_val_if_fail (content_type, NULL);
+
+ modest_text_utils_strftime (sent_str, 100, "%c", sent_date);
+
+ 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
+ retval = modest_text_utils_quote_plain_text (text, cited, signature, NULL, 80);
+
+ g_free (cited);
+ return retval;
+}
+
+/* just to prevent warnings:
+ * warning: `%x' yields only last 2 digits of year in some locales
+ */
+gsize
+modest_text_utils_strftime(char *s, gsize max, const char *fmt, time_t timet)
+{
+ struct tm tm;
+
+ /* 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;
+
+ g_return_val_if_fail (prefix, NULL);
+
+ if (!subject || subject[0] == '\0')
+ subject = _("mail_va_no_subject");
+
+ tmp = g_strchug (g_strdup (subject));
+
+ if (!strncmp (tmp, prefix, strlen (prefix))) {
+ return tmp;
+ } else {
+ g_free (tmp);
+ return g_strdup_printf ("%s %s", prefix, subject);
+ }
+}
+
+gchar*
+modest_text_utils_remove_address (const gchar *address_list, const gchar *address)
+{
+ 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 *) 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 (filtered_emails->len == 0)
+ g_string_append_printf (filtered_emails, "%s", g_strstrip (token));
+ else
+ g_string_append_printf (filtered_emails, ",%s", g_strstrip (token));
+ }
+ token = strtok_r (NULL, ",", &ptr);
+ }
+ 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;
+
+ g_return_val_if_fail (address_list, NULL);
+
+ table = g_hash_table_new (g_str_hash, g_str_equal);
+ addresses = modest_text_utils_split_addresses_list (address_list);
+
+ new_list = g_strdup("");
+ cursor = addresses;
+ while (cursor) {
+ const gchar* address = (const gchar*)cursor->data;
+
+ /* ignore the address if already seen */
+ if (g_hash_table_lookup (table, address) == 0) {
+
+ gchar *tmp = g_strjoin (",", new_list, address, NULL);
+ g_free (new_list);
+ new_list = tmp;
+
+ g_hash_table_insert (table, (gchar*)address, GINT_TO_POINTER(1));
+ }
+ cursor = g_slist_next (cursor);
+ }
+
+ g_hash_table_destroy (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_start (GString *html, const gchar *data, gssize n)
+{
+ guint i;
+ gboolean space_seen = FALSE;
+ guint break_dist = 0; /* distance since last break point */
+
+ if (n == -1)
+ n = strlen (data);
+
+ /* replace with special html chars where needed*/
+ for (i = 0; i != n; ++i) {
+ guchar kar = data[i];
+
+ if (space_seen && kar != ' ') {
+ g_string_append_c (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:
+ 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, "<br>\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, " ");
+ space_seen = FALSE;
+ } else
+ space_seen = TRUE;
+ break;
+ default:
+ g_string_append_c (html, kar);
+ }
+ }
+}
+
+
+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;
+
+ len = strlen (data);
+ html = g_string_sized_new (1.5 * len); /* just a guess... */
+
+ g_string_append_printf (html,
+ "<html><head>"
+ "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf8\">"
+ "</head>"
+ "<body>");
+
+ modest_text_utils_convert_buffer_to_html_start (html, data, -1);
+
+ g_string_append (html, "</body></html>");
+
+ if (len <= HYPERLINKIFY_MAX_LENGTH)
+ hyperlinkify_plain_text (html);
+
+ 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, gssize n, gboolean hyperlinkify)
+{
+ GString *html;
+
+ g_return_val_if_fail (data, NULL);
+
+ if (!data)
+ return NULL;
+
+ if (n == -1)
+ n = strlen (data);
+ html = g_string_sized_new (1.5 * n); /* just a guess... */
+
+ modest_text_utils_convert_buffer_to_html_start (html, data, n);
+
+ if (hyperlinkify && (n < HYPERLINKIFY_MAX_LENGTH))
+ hyperlinkify_plain_text (html);
+
+ modest_text_utils_convert_buffer_to_html_finish (html);
+
+ return g_string_free (html, FALSE);
+}
+
+void
+modest_text_utils_get_addresses_indexes (const gchar *addresses, GSList **start_indexes, GSList **end_indexes)
+{
+ gchar *current, *start, *last_blank;
+ gint start_offset = 0, current_offset = 0;
+
+ g_return_if_fail (start_indexes != NULL);
+ g_return_if_fail (end_indexes != NULL);
+
+ start = (gchar *) addresses;
+ current = start;
+ last_blank = start;
+
+ while (*current != '\0') {
+ if ((start == current)&&((*current == ' ')||(*current == ',')||(*current == ';'))) {
+ start = g_utf8_next_char (start);
+ start_offset++;
+ last_blank = current;
+ } else if ((*current == ',')||(*current == ';')) {
+ gint *start_index, *end_index;
+ start_index = g_new0(gint, 1);
+ end_index = g_new0(gint, 1);
+ *start_index = start_offset;
+ *end_index = current_offset;
+ *start_indexes = g_slist_prepend (*start_indexes, start_index);
+ *end_indexes = g_slist_prepend (*end_indexes, end_index);
+ start = g_utf8_next_char (current);
+ start_offset = current_offset + 1;
+ last_blank = start;
+ } else if (*current == '"') {
+ current = g_utf8_next_char (current);
+ current_offset ++;
+ while ((*current != '"')&&(*current != '\0')) {
+ current = g_utf8_next_char (current);
+ current_offset ++;
+ }
+ }
+
+ current = g_utf8_next_char (current);
+ current_offset ++;
+ }
+
+ if (start != current) {
+ gint *start_index, *end_index;
+ start_index = g_new0(gint, 1);
+ end_index = g_new0(gint, 1);
+ *start_index = start_offset;
+ *end_index = current_offset;
+ *start_indexes = g_slist_prepend (*start_indexes, start_index);
+ *end_indexes = g_slist_prepend (*end_indexes, end_index);
+ }
+
+ *start_indexes = g_slist_reverse (*start_indexes);
+ *end_indexes = g_slist_reverse (*end_indexes);
+
+ return;
+}
+
+
+GSList *
+modest_text_utils_split_addresses_list (const gchar *addresses)
+{
+ GSList *head;
+ 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] == ';'))
+ ++my_addrs;
+
+ /* 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] != ',' && end[0] != ';')
+ ++end;
+
+ /* we got the address; copy it and remove trailing whitespace */
+ addr = g_strndup (my_addrs, end - my_addrs);
+ g_strchomp (addr);
+
+ 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,
+ guint *start,
+ guint *end)
+{
+ gchar *current = NULL;
+ gint range_start = 0;
+ gint range_end = 0;
+ 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)) {
+ gunichar c = g_utf8_get_char (current);
+
+ if ((c == ',') && (!is_quoted)) {
+ if (index < position) {
+ range_start = index + 1;
+ } else {
+ break;
+ }
+ } else if (c == '\"') {
+ is_quoted = !is_quoted;
+ } else if ((c == ' ') &&(range_start == index)) {
+ range_start ++;
+ }
+ index ++;
+ range_end = index;
+ }
+
+ if (start)
+ *start = range_start;
+ if (end)
+ *end = range_end;
+}
+
+
+/* ******************************************************************* */
+/* ************************* UTILIY FUNCTIONS ************************ */
+/* ******************************************************************* */