+ append_quoted (q, 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);
+
+ if (signature != NULL) {
+ q = g_string_append_c (q, '\n');
+ q = g_string_append (q, signature);
+ }
+
+ return g_string_free (q, FALSE);
+}
+
+static gchar*
+modest_text_utils_quote_html (const gchar *text,
+ const gchar *cite,
+ const gchar *signature,
+ GList *attachments,
+ int limit)
+{
+ gchar *result = NULL;
+ gchar *signature_result = NULL;
+ const gchar *format = \
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" \
+ "<html>\n" \
+ "<body>\n" \
+ "<br/>%s<br/>" \
+ "<pre>%s<br/>%s<br/>%s</pre>\n" \
+ "</body>\n" \
+ "</html>\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);
+
+ 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;
+}
+
+static gint
+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 url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
+
+
+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 */
+ g_return_val_if_fail (regcomp (patterns[i].preg, patterns[i].regex,
+ REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0, 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_matches_block == 0)
+ compile_patterns ();
+ url_matches_block ++;
+}
+
+void
+modest_text_utils_hyperlinkify_end (void)
+{
+ url_matches_block--;
+ if (url_matches_block <= 0)
+ free_patterns ();
+}
+
+
+static GSList*
+get_url_matches (GString *txt)
+{
+ regmatch_t rm;
+ guint rv, i, offset = 0;
+ GSList *match_list = NULL;
+
+ const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t);
+
+ /* initalize the regexps */
+ modest_text_utils_hyperlinkify_begin ();
+
+ /* find all the matches */
+ for (i = 0; i != pattern_num; ++i) {
+ offset = 0;
+ while (1) {
+ url_match_t *match;
+ gboolean is_submatch;
+ GSList *cursor;
+
+ if ((rv = regexec (patterns[i].preg, txt->str + 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;
+
+ 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 = 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 (<offset, len, prefix> tupels)*/
+ match = g_slice_new (url_match_t);
+ match->offset = 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;
+ }
+ }
+
+ 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
+ * to recalculate the offsets
+ */
+ match_list = g_slist_sort (match_list,
+ (GCompareFunc)cmp_offsets_reverse);
+ return match_list;
+}
+
+
+
+static void
+hyperlinkify_plain_text (GString *txt)
+{
+ GSList *cursor;
+ GSList *match_list = get_url_matches (txt);
+
+ /* we will work backwards, so the offsets stay valid */
+ for (cursor = match_list; cursor; cursor = cursor->next) {
+
+ url_match_t *match = (url_match_t*) cursor->data;
+ gchar *url = g_strndup (txt->str + match->offset, match->len);
+ gchar *repl = NULL; /* replacement */
+
+ /* the prefix is NULL: use the one that is already there */
+ repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
+ match->prefix ? match->prefix : EMPTY_STRING,
+ url, url);
+
+ /* replace the old thing with our hyperlink
+ * replacement thing */
+ g_string_erase (txt, match->offset, match->len);
+ g_string_insert (txt, match->offset, repl);
+
+ g_free (url);
+ g_free (repl);
+
+ g_slice_free (url_match_t, match);
+ }
+
+ g_slist_free (match_list);
+}
+
+
+/* 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;
+
+ /* 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);
+ * */
+
+ /* remove leading whitespace */
+ if (address[0] == ' ')
+ g_strchug (address);
+
+ for (i = 0; address[i]; ++i) {
+ if (address[i] == '<') {
+ if (G_UNLIKELY(i == 0))
+ return; /* there's nothing else, leave it */
+ else {
+ address[i] = '\0'; /* terminate the string here */
+ return;
+ }
+ }
+ }
+}
+
+
+
+
+
+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 prefix_len = 0;
+
+ g_return_val_if_fail (sub, 0);
+
+ if (!sub)
+ return 0;
+
+ /* 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;
+ }
+
+ /* 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)
+{
+
+/* work even when s1 and/or s2 == NULL */
+ if (G_UNLIKELY(s1 == s2))
+ return 0;
+ if (G_UNLIKELY(!s1))
+ return -1;
+ if (G_UNLIKELY(!s2))
+ return 1;
+
+ /* if it's not case sensitive */
+ if (!insensitive) {
+
+ /* optimization: short cut if first char is ascii */
+ if (((s1[0] & 0xf0)== 0) && ((s2[0] & 0xf0) == 0))
+ return s1[0] - s2[0];
+
+ return g_utf8_collate (s1, s2);
+
+ } else {
+ gint result;
+ gchar *n1, *n2;
+
+ /* optimization: short cut iif first char is ascii */
+ if (((s1[0] & 0xf0) == 0) && ((s2[0] & 0xf0) == 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;
+ }
+}
+
+
+const gchar*
+modest_text_utils_get_display_date (time_t date)
+{
+#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 */
+
+ 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);
+
+ 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", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6",
+ "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+ ".", "..", NULL
+ };
+
+ /* cannot be NULL */
+ if (!folder_name)
+ return FALSE;
+
+ /* cannot be empty */
+ len = strlen(folder_name);
+ if (len == 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 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_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);