* AUTHORS
[claws.git] / src / common / utils.c
index 7c2873680ed4a979bcf3b1c182f52039d9464ab4..fab9d2311d88fdda1a346edd66fa230d2402e3a6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2002 Hiroyuki Yamamoto
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -41,6 +41,7 @@
 #include <sys/wait.h>
 #include <dirent.h>
 #include <time.h>
+#include <regex.h>
 
 #include "intl.h"
 #include "utils.h"
@@ -109,6 +110,25 @@ void hash_free_value_mem(GHashTable *table)
        g_hash_table_foreach(table, hash_free_value_mem_func, NULL);
 }
 
+gint str_case_equal(gconstpointer v, gconstpointer v2)
+{
+       return strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
+}
+
+guint str_case_hash(gconstpointer key)
+{
+       const gchar *p = key;
+       guint h = *p;
+
+       if (h) {
+               h = tolower(h);
+               for (p += 1; *p != '\0'; p++)
+                       h = (h << 5) - h + tolower(*p);
+       }
+
+       return h;
+}
+
 void ptr_array_free_strings(GPtrArray *array)
 {
        gint i;
@@ -155,13 +175,13 @@ gchar *to_human_readable(off_t size)
        static gchar str[10];
 
        if (size < 1024)
-               g_snprintf(str, sizeof(str), "%dB", (gint)size);
+               g_snprintf(str, sizeof(str), _("%dB"), (gint)size);
        else if (size >> 10 < 1024)
-               g_snprintf(str, sizeof(str), "%.1fKB", (gfloat)size / (1 << 10));
+               g_snprintf(str, sizeof(str), _("%.1fKB"), (gfloat)size / (1 << 10));
        else if (size >> 20 < 1024)
-               g_snprintf(str, sizeof(str), "%.2fMB", (gfloat)size / (1 << 20));
+               g_snprintf(str, sizeof(str), _("%.2fMB"), (gfloat)size / (1 << 20));
        else
-               g_snprintf(str, sizeof(str), "%.2fGB", (gfloat)size / (1 << 30));
+               g_snprintf(str, sizeof(str), _("%.2fGB"), (gfloat)size / (1 << 30));
 
        return str;
 }
@@ -469,53 +489,54 @@ wchar_t *wcscasestr(const wchar_t *haystack, const wchar_t *needle)
        return NULL;
 }
 
-/* Examine if next block is non-ASCII string */
-gboolean is_next_nonascii(const wchar_t *s)
+gint get_mbs_len(const gchar *s)
 {
-       const wchar_t *wp;
+       const gchar *p = s;
+       gint mb_len;
+       gint len = 0;
 
-       /* skip head space */
-       for (wp = s; *wp != (wchar_t)0 && iswspace(*wp); wp++)
-               ;
-       for (; *wp != (wchar_t)0 && !iswspace(*wp); wp++) {
-               if (*wp > 127)
-                       return TRUE;
+       if (!p)
+               return -1;
+
+       while (*p != '\0') {
+               mb_len = mblen(p, MB_LEN_MAX);
+               if (mb_len == 0)
+                       break;
+               else if (mb_len < 0)
+                       return -1;
+               else
+                       len++;
+
+               p += mb_len;
        }
 
-       return FALSE;
+       return len;
 }
 
-/* Examine if next block is multi-byte string */
-gboolean is_next_mbs(const wchar_t *s)
+/* Examine if next block is non-ASCII string */
+gboolean is_next_nonascii(const guchar *s)
 {
-       gint mbl;
-       const wchar_t *wp;
-       gchar tmp[MB_LEN_MAX];
+       const guchar *p;
 
        /* skip head space */
-       for (wp = s; *wp != (wchar_t)0 && iswspace(*wp); wp++)
+       for (p = s; *p != '\0' && isspace(*p); p++)
                ;
-       for (; *wp != (wchar_t)0 && !iswspace(*wp); wp++) {
-               mbl = wctomb(tmp, *wp);
-               if (mbl > 1)
+       for (; *p != '\0' && !isspace(*p); p++) {
+               if (*p > 127 || *p < 32)
                        return TRUE;
        }
 
        return FALSE;
 }
 
-wchar_t *find_wspace(const wchar_t *s)
+gint get_next_word_len(const gchar *s)
 {
-       const wchar_t *wp;
+       gint len = 0;
 
-       for (wp = s; *wp != (wchar_t)0 && iswspace(*wp); wp++)
+       for (; *s != '\0' && !isspace(*s); s++, len++)
                ;
-       for (; *wp != (wchar_t)0; wp++) {
-               if (iswspace(*wp))
-                       return (wchar_t *)wp;
-       }
 
-       return NULL;
+       return len;
 }
 
 /* compare subjects */
@@ -529,27 +550,84 @@ gint subject_compare(const gchar *s1, const gchar *s2)
        Xstrdup_a(str1, s1, return -1);
        Xstrdup_a(str2, s2, return -1);
 
-       trim_subject(str1);
-       trim_subject(str2);
+       trim_subject_for_compare(str1);
+       trim_subject_for_compare(str2);
 
        if (!*str1 || !*str2) return -1;
 
        return strcmp(str1, str2);
 }
 
-void trim_subject(gchar *str)
+gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
+{
+       gchar *str1, *str2;
+
+       if (!s1 || !s2) return -1;
+
+       Xstrdup_a(str1, s1, return -1);
+       Xstrdup_a(str2, s2, return -1);
+
+       trim_subject_for_sort(str1);
+       trim_subject_for_sort(str2);
+
+       return strcasecmp(str1, str2);
+}
+
+void trim_subject_for_compare(gchar *str)
 {
        gchar *srcp;
+       int skip;
 
        eliminate_parenthesis(str, '[', ']');
        eliminate_parenthesis(str, '(', ')');
        g_strstrip(str);
 
-       while (!strncasecmp(str, "Re:", 3)) {
-               srcp = str + 3;
-               while (isspace(*srcp)) srcp++;
+       srcp = str + subject_get_reply_prefix_length(str);
+       if (srcp != str)
                memmove(str, srcp, strlen(srcp) + 1);
+}
+
+void trim_subject_for_sort(gchar *str)
+{
+       gchar *srcp;
+
+       g_strstrip(str);
+
+       srcp = str + subject_get_reply_prefix_length(str);
+       if (srcp != str)        
+               memmove(str, srcp, strlen(srcp) + 1);
+}
+
+void trim_subject(gchar *str)
+{
+       register gchar *srcp, *destp;
+       gchar op, cl;
+       gint in_brace;
+
+       destp = str + subject_get_reply_prefix_length(str);
+
+       if (*destp == '[') {
+               op = '[';
+               cl = ']';
+       } else if (*destp == '(') {
+               op = '(';
+               cl = ')';
+       } else
+               return;
+
+       srcp = destp + 1;
+       in_brace = 1;
+       while (*srcp) {
+               if (*srcp == op)
+                       in_brace++;
+               else if (*srcp == cl)
+                       in_brace--;
+               srcp++;
+               if (in_brace == 0)
+                       break;
        }
+       while (isspace(*srcp)) srcp++;
+       memmove(destp, srcp, strlen(srcp) + 1);
 }
 
 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
@@ -603,35 +681,6 @@ void extract_parenthesis(gchar *str, gchar op, gchar cl)
        *destp = '\0';
 }
 
-void extract_one_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
-                                            gchar op, gchar cl)
-{
-       register gchar *srcp, *destp;
-       gint in_brace;
-       gboolean in_quote = FALSE;
-
-       srcp = destp = str;
-
-       if ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
-               memmove(destp, srcp + 1, strlen(srcp));
-               in_brace = 1;
-               while(*destp) {
-                       if (*destp == op && !in_quote)
-                               in_brace++;
-                       else if (*destp == cl && !in_quote)
-                               in_brace--;
-                       else if (*destp == quote_chr)
-                               in_quote ^= TRUE;
-
-                       if (in_brace == 0)
-                               break;
-
-                       destp++;
-               }
-       }
-       *destp = '\0';
-}
-
 void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
                                         gchar op, gchar cl)
 {
@@ -779,7 +828,14 @@ void extract_address(gchar *str)
        g_strstrip(str);
 }
 
-GSList *address_list_append(GSList *addr_list, const gchar *str)
+void extract_list_id_str(gchar *str)
+{
+       if (strchr_with_skip_quote(str, '"', '<'))
+               extract_parenthesis_with_skip_quote(str, '"', '<', '>');
+       g_strstrip(str);
+}
+
+static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
 {
        gchar *work;
        gchar *workp;
@@ -788,7 +844,8 @@ GSList *address_list_append(GSList *addr_list, const gchar *str)
 
        Xstrdup_a(work, str, return addr_list);
 
-       eliminate_address_comment(work);
+       if (removecomments)
+               eliminate_address_comment(work);
        workp = work;
 
        while (workp && *workp) {
@@ -800,7 +857,7 @@ GSList *address_list_append(GSList *addr_list, const gchar *str)
                } else
                        next = NULL;
 
-               if (strchr_with_skip_quote(workp, '"', '<'))
+               if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
                        extract_parenthesis_with_skip_quote
                                (workp, '"', '<', '>');
 
@@ -814,6 +871,16 @@ GSList *address_list_append(GSList *addr_list, const gchar *str)
        return addr_list;
 }
 
+GSList *address_list_append(GSList *addr_list, const gchar *str)
+{
+       return address_list_append_real(addr_list, str, TRUE);
+}
+
+GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
+{
+       return address_list_append_real(addr_list, str, FALSE);
+}
+
 GSList *references_list_append(GSList *msgid_list, const gchar *str)
 {
        const gchar *strp;
@@ -988,6 +1055,8 @@ gboolean is_header_line(const gchar *str)
 
 gboolean is_ascii_str(const guchar *str)
 {
+       g_return_val_if_fail(str, FALSE);
+
        while (*str != '\0') {
                if (*str != '\t' && *str != ' ' &&
                    *str != '\r' && *str != '\n' &&
@@ -1270,22 +1339,23 @@ gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
        gchar *abbrev_group;
        gchar *ap;
        const gchar *p = group;
-       gint  count = 0;
+       const gchar *last;
+
+       g_return_val_if_fail(group != NULL, NULL);
 
+       last = group + strlen(group);
        abbrev_group = ap = g_malloc(strlen(group) + 1);
 
        while (*p) {
                while (*p == '.')
                        *ap++ = *p++;
-
-               if ((strlen( p) + count) > len && strchr(p, '.')) {
+               if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
                        *ap++ = *p++;
                        while (*p != '.') p++;
                } else {
-                       strcpy( ap, p);
+                       strcpy(ap, p);
                        return abbrev_group;
                }
-               count = count + 2;
        }
 
        *ap = '\0';
@@ -2345,6 +2415,39 @@ gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
 /* convert line endings into CRLF. If the last line doesn't end with
  * linebreak, add it.
  */
+gchar *canonicalize_str(const gchar *str)
+{
+       const gchar *p;
+       guint new_len = 0;
+       gchar *out, *outp;
+
+       for (p = str; *p != '\0'; ++p) {
+               if (*p != '\r') {
+                       ++new_len;
+                       if (*p == '\n')
+                               ++new_len;
+               }
+       }
+       if (p == str || *(p - 1) != '\n')
+               new_len += 2;
+
+       out = outp = g_malloc(new_len + 1);
+       for (p = str; *p != '\0'; ++p) {
+               if (*p != '\r') {
+                       if (*p == '\n')
+                               *outp++ = '\r';
+                       *outp++ = *p;
+               }
+       }
+       if (p == str || *(p - 1) != '\n') {
+               *outp++ = '\r';
+               *outp++ = '\n';
+       }
+       *outp = '\0';
+
+       return out;
+}
+
 gint canonicalize_file(const gchar *src, const gchar *dest)
 {
        FILE *src_fp, *dest_fp;
@@ -2406,7 +2509,7 @@ gint canonicalize_file(const gchar *src, const gchar *dest)
        }
 
        if (ferror(src_fp)) {
-               FILE_OP_ERROR(src, "fread");
+               FILE_OP_ERROR(src, "fgets");
                err = TRUE;
        }
        fclose(src_fp);
@@ -2445,6 +2548,146 @@ gint canonicalize_file_replace(const gchar *file)
        return 0;
 }
 
+gint uncanonicalize_file(const gchar *src, const gchar *dest)
+{
+       FILE *src_fp, *dest_fp;
+       gchar buf[BUFFSIZE];
+       gboolean err = FALSE;
+
+       if ((src_fp = fopen(src, "rb")) == NULL) {
+               FILE_OP_ERROR(src, "fopen");
+               return -1;
+       }
+
+       if ((dest_fp = fopen(dest, "wb")) == NULL) {
+               FILE_OP_ERROR(dest, "fopen");
+               fclose(src_fp);
+               return -1;
+       }
+
+       if (change_file_mode_rw(dest_fp, dest) < 0) {
+               FILE_OP_ERROR(dest, "chmod");
+               g_warning("can't change file mode\n");
+       }
+
+       while (fgets(buf, sizeof(buf), src_fp) != NULL) {
+               strcrchomp(buf);
+               if (fputs(buf, dest_fp) == EOF) {
+                       g_warning("writing to %s failed.\n", dest);
+                       fclose(dest_fp);
+                       fclose(src_fp);
+                       unlink(dest);
+                       return -1;
+               }
+       }
+
+       if (ferror(src_fp)) {
+               FILE_OP_ERROR(src, "fgets");
+               err = TRUE;
+       }
+       fclose(src_fp);
+       if (fclose(dest_fp) == EOF) {
+               FILE_OP_ERROR(dest, "fclose");
+               err = TRUE;
+       }
+
+       if (err) {
+               unlink(dest);
+               return -1;
+       }
+
+       return 0;
+}
+
+gint uncanonicalize_file_replace(const gchar *file)
+{
+       gchar *tmp_file;
+
+       tmp_file = get_tmp_file();
+
+       if (uncanonicalize_file(file, tmp_file) < 0) {
+               g_free(tmp_file);
+               return -1;
+       }
+
+       if (move_file(tmp_file, file, TRUE) < 0) {
+               g_warning("can't replace %s .\n", file);
+               unlink(tmp_file);
+               g_free(tmp_file);
+               return -1;
+       }
+
+       g_free(tmp_file);
+       return 0;
+}
+
+gchar *normalize_newlines(const gchar *str)
+{
+       const gchar *p = str;
+       gchar *out, *outp;
+
+       out = outp = g_malloc(strlen(str) + 1);
+       for (p = str; *p != '\0'; ++p) {
+               if (*p == '\r') {
+                       if (*(p + 1) != '\n')
+                               *outp++ = '\n';
+               } else
+                       *outp++ = *p;
+       }
+
+       *outp = '\0';
+
+       return out;
+}
+
+gchar *get_outgoing_rfc2822_str(FILE *fp)
+{
+       gchar buf[BUFFSIZE];
+       GString *str;
+       gchar *ret;
+
+       str = g_string_new(NULL);
+
+       /* output header part */
+       while (fgets(buf, sizeof(buf), fp) != NULL) {
+               strretchomp(buf);
+               if (!g_strncasecmp(buf, "Bcc:", 4)) {
+                       gint next;
+
+                       for (;;) {
+                               next = fgetc(fp);
+                               if (next == EOF)
+                                       break;
+                               else if (next != ' ' && next != '\t') {
+                                       ungetc(next, fp);
+                                       break;
+                               }
+                               if (fgets(buf, sizeof(buf), fp) == NULL)
+                                       break;
+                       }
+               } else {
+                       g_string_append(str, buf);
+                       g_string_append(str, "\r\n");
+                       if (buf[0] == '\0')
+                               break;
+               }
+       }
+
+       /* output body part */
+       while (fgets(buf, sizeof(buf), fp) != NULL) {
+               strretchomp(buf);
+               if (buf[0] == '.')
+                       g_string_append_c(str, '.');
+               g_string_append(str, buf);
+               g_string_append(str, "\r\n");
+       }
+
+       ret = str->str;
+       g_string_free(str, FALSE);
+
+       return ret;
+}
+
 gint change_file_mode_rw(FILE *fp, const gchar *file)
 {
 #if HAVE_FCHMOD
@@ -2557,10 +2800,7 @@ gint str_write_to_file(const gchar *str, const gchar *file)
 
 gchar *file_read_to_str(const gchar *file)
 {
-       GByteArray *array;
        FILE *fp;
-       gchar buf[BUFSIZ];
-       gint n_read;
        gchar *str;
 
        g_return_val_if_fail(file != NULL, NULL);
@@ -2570,6 +2810,22 @@ gchar *file_read_to_str(const gchar *file)
                return NULL;
        }
 
+       str = file_read_stream_to_str(fp);
+
+       fclose(fp);
+
+       return str;
+}
+
+gchar *file_read_stream_to_str(FILE *fp)
+{
+       GByteArray *array;
+       gchar buf[BUFSIZ];
+       gint n_read;
+       gchar *str;
+
+       g_return_val_if_fail(fp != NULL, NULL);
+
        array = g_byte_array_new();
 
        while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
@@ -2579,14 +2835,11 @@ gchar *file_read_to_str(const gchar *file)
        }
 
        if (ferror(fp)) {
-               FILE_OP_ERROR(file, "fread");
-               fclose(fp);
+               FILE_OP_ERROR("file stream", "fread");
                g_byte_array_free(array, TRUE);
                return NULL;
        }
 
-       fclose(fp);
-
        buf[0] = '\0';
        g_byte_array_append(array, buf, 1);
        str = (gchar *)array->data;
@@ -2664,6 +2917,33 @@ gint execute_command_line(const gchar *cmdline, gboolean async)
        return ret;
 }
 
+gchar *get_command_output(const gchar *cmdline)
+{
+       gchar buf[BUFFSIZE];
+       FILE *fp;
+       GString *str;
+       gchar *ret;
+
+       g_return_val_if_fail(cmdline != NULL, NULL);
+
+       if ((fp = popen(cmdline, "r")) == NULL) {
+               FILE_OP_ERROR(cmdline, "popen");
+               return NULL;
+       }
+
+       str = g_string_new("");
+
+       while (fgets(buf, sizeof(buf), fp) != NULL)
+               g_string_append(str, buf);
+
+       pclose(fp);
+
+       ret = str->str;
+       g_string_free(str, FALSE);
+
+       return ret;
+}
+
 static gint is_unchanged_uri_char(char c)
 {
        switch (c) {
@@ -2774,7 +3054,6 @@ void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
 
 gint open_uri(const gchar *uri, const gchar *cmdline)
 {
-       static gchar *default_cmdline = "netscape -remote openURL(%s,raise)";
        gchar buf[BUFFSIZE];
        gchar *p;
        gchar encoded_uri[BUFFSIZE];
@@ -2792,7 +3071,7 @@ gint open_uri(const gchar *uri, const gchar *cmdline)
                if (cmdline)
                        g_warning("Open URI command line is invalid: `%s'",
                                  cmdline);
-               g_snprintf(buf, sizeof(buf), default_cmdline, encoded_uri);
+               g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
        }
        
        execute_command_line(buf, TRUE);
@@ -2822,11 +3101,16 @@ time_t remote_tzoffset_sec(const gchar *zone)
        } else if (!strncmp(zone, "UT" , 2) ||
                   !strncmp(zone, "GMT", 2)) {
                remoteoffset = 0;
-       } else if (strlen(zone3) == 3 &&
-                  (p = strstr(ustzstr, zone3)) != NULL &&
-                  (p - ustzstr) % 3 == 0) {
-               iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
-               remoteoffset = iustz * 3600;
+       } else if (strlen(zone3) == 3) {
+               for (p = ustzstr; *p != '\0'; p += 3) {
+                       if (!strncasecmp(p, zone3, 3)) {
+                               iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
+                               remoteoffset = iustz * 3600;
+                               break;
+                       }
+               }
+               if (*p == '\0')
+                       return -1;
        } else if (strlen(zone3) == 1) {
                switch (zone[0]) {
                case 'Z': remoteoffset =   0; break;
@@ -2857,7 +3141,8 @@ time_t remote_tzoffset_sec(const gchar *zone)
                default:  remoteoffset =   0; break;
                }
                remoteoffset = remoteoffset * 3600;
-       }
+       } else
+               return -1;
 
        return remoteoffset;
 }
@@ -2885,10 +3170,6 @@ time_t tzoffset_sec(time_t *now)
                off = 23 * 60 + 59;     /* if not, insert silly value */
        if (off <= -24 * 60)
                off = -(23 * 60 + 59);
-       if (off > 12 * 60)
-               off -= 24 * 60;
-       if (off < -12 * 60)
-               off += 24 * 60;
 
        return off * 60;
 }
@@ -2949,7 +3230,7 @@ void debug_set_mode(gboolean mode)
        debug_mode = mode;
 }
 
-gboolean debug_get_mode()
+gboolean debug_get_mode(void)
 {
        return debug_mode;
 }
@@ -2972,29 +3253,19 @@ void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
 {
        if (subject == NULL)
                subject = "";
-
-       if (g_strncasecmp(subject, "Re: ", 4) == 0)
-               return g_hash_table_lookup(subject_table, subject + 4);
        else
-               return g_hash_table_lookup(subject_table, subject);
+               subject += subject_get_reply_prefix_length(subject);
+
+       return g_hash_table_lookup(subject_table, subject);
 }
 
 void subject_table_insert(GHashTable *subject_table, gchar * subject,
                          void * data)
 {
-       if (subject == NULL)
-               return;
-       if (* subject == 0)
-               return;
-       if (g_strcasecmp(subject, "Re:") == 0)
+       if (subject == NULL || *subject == 0)
                return;
-       if (g_strcasecmp(subject, "Re: ") == 0)
-               return;
-
-       if (g_strncasecmp(subject, "Re: ", 4) == 0)
-               g_hash_table_insert(subject_table, subject + 4, data);
-       else
-               g_hash_table_insert(subject_table, subject, data);
+       subject += subject_get_reply_prefix_length(subject);
+       g_hash_table_insert(subject_table, subject, data);
 }
 
 void subject_table_remove(GHashTable *subject_table, gchar * subject)
@@ -3002,19 +3273,72 @@ void subject_table_remove(GHashTable *subject_table, gchar * subject)
        if (subject == NULL)
                return;
 
-       if (g_strncasecmp(subject, "Re: ", 4) == 0)
-               g_hash_table_remove(subject_table, subject + 4);
-       else
-               g_hash_table_remove(subject_table, subject);
+       subject += subject_get_reply_prefix_length(subject);    
+       g_hash_table_remove(subject_table, subject);
 }
 
-gboolean subject_is_reply(const gchar *subject)
-{
-       /* XXX: just simply here so someone can handle really
-        * advanced Re: detection like "Re[4]", "ANTW:" or
-        * Re: Re: Re: Re: Re: Re: Re: Re:" stuff. */
-       if (subject == NULL) return FALSE;
-       else return 0 == g_strncasecmp(subject, "Re: ", 4);
+/*!
+ *\brief       Check if a string is prefixed with known (combinations) 
+ *             of reply prefixes. The function assumes that each prefix 
+ *             is terminated by zero or exactly _one_ space.
+ *
+ *\param       str String to check for a prefixes
+ *
+ *\return      int Number of chars in the prefix that should be skipped 
+ *             for a "clean" subject line. If no prefix was found, 0
+ *             is returned.
+ */            
+int subject_get_reply_prefix_length(const gchar *subject)
+{
+       /*!< Array with allowable reply prefixes regexps. */
+       static const gchar * const reply_prefixes[] = {
+               "[Rr][Ee]\\:",                  /* "Re:" */
+               "[Rr][Ee]\\[[1-9][0-9]*\\]\\:", /* Intelligent but stupidly non-conforming Re[XXX]:*/
+               "[Aa][Nn][Tt][Ww]\\:",          /* Overactive i18n / translation teams */
+               "[Aa][Ww]\\:"                   /* "Aw:" */
+               /* add more */
+       };
+       const int REPLY_PREFIXES = sizeof reply_prefixes / sizeof reply_prefixes[0];
+       int n;
+       regmatch_t pos;
+       static regex_t regex;
+       static gboolean init_;
+
+       if (!subject) return 0;
+       if (!*subject) return 0;
+
+       if (!init_) {
+               GString *s = g_string_new("");
+               
+               for (n = 0; n < REPLY_PREFIXES; n++)
+                       /* Terminate each prefix regexpression by a
+                        * "\ ?" (zero or ONE space), and OR them */
+                       g_string_sprintfa(s, "(%s\\ ?)%s",
+                                         reply_prefixes[n],
+                                         n < REPLY_PREFIXES - 1 ? 
+                                         "|" : "");
+               
+               g_string_prepend(s, "(");
+               g_string_append(s, ")+");       /* match at least once */
+               g_string_prepend(s, "^\\ *");   /* from beginning of line */
+               
+
+               /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+" 
+                * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
+               if (regcomp(&regex, s->str, REG_EXTENDED)) { 
+                       debug_print("Error compiling regexp %s\n", s->str);
+                       g_string_free(s, TRUE);
+                       return 0;
+               } else {
+                       init_ = TRUE;
+                       g_string_free(s, TRUE);
+               }
+       }
+       
+       if (!regexec(&regex, subject, 1, &pos, 0) && pos.rm_so != -1)
+               return pos.rm_eo;
+       else
+               return 0;
 }
 
 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
@@ -3030,10 +3354,11 @@ FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
 /* allow Mutt-like patterns in quick search */
 gchar *expand_search_string(const gchar *search_string)
 {
-       int i, len, new_len = 0;
+       int i = 0;
        gchar term_char, save_char;
        gchar *cmd_start, *cmd_end;
-       gchar *new_str = NULL;
+       GString *matcherstr;
+       gchar *returnstr = NULL;
        gchar *copy_str;
        gboolean casesens, dontmatch;
        /* list of allowed pattern abbreviations */
@@ -3060,6 +3385,7 @@ gchar *expand_search_string(const gchar *search_string)
                { "h",  "headers_part",                 1,      TRUE,   TRUE  },
                { "i",  "header \"Message-Id\"",        1,      TRUE,   TRUE  },
                { "I",  "inreplyto",                    1,      TRUE,   TRUE  },
+               { "L",  "locked",                       0,      FALSE,  FALSE },
                { "n",  "newsgroups",                   1,      TRUE,   TRUE  },
                { "N",  "new",                          0,      FALSE,  FALSE },
                { "O",  "~new",                         0,      FALSE,  FALSE },
@@ -3101,6 +3427,7 @@ gchar *expand_search_string(const gchar *search_string)
        if (cmds[i].command)
                return copy_str;
 
+       matcherstr = g_string_sized_new(16);
        cmd_start = cmd_end = copy_str;
        while (cmd_end && *cmd_end) {
                /* skip all white spaces */
@@ -3136,26 +3463,15 @@ gchar *expand_search_string(const gchar *search_string)
                        if (!strcmp(cmd_start, cmds[i].abbreviated)) {
                                /* restore character */
                                *cmd_end = save_char;
-                               len = strlen(cmds[i].command) + 1;
-                               if (dontmatch)
-                                       len++;
-                               if (casesens)
-                                       len++;
 
                                /* copy command */
-                               if (new_str) {
-                                       new_len += 1;
-                                       new_str = g_realloc(new_str, new_len);
-                                       strcat(new_str, " ");
+                               if (matcherstr->len > 0) {
+                                       g_string_append(matcherstr, " ");
                                }
-                               new_len += (len + 1);
-                               new_str = g_realloc(new_str, new_len);
-                               if (new_len == len + 1)
-                                       *new_str = '\0';
                                if (dontmatch)
-                                       strcat(new_str, "~");
-                               strcat(new_str, cmds[i].command);
-                               strcat(new_str, " ");
+                                       g_string_append(matcherstr, "~");
+                               g_string_append(matcherstr, cmds[i].command);
+                               g_string_append(matcherstr, " ");
 
                                /* stop if no params required */
                                if (cmds[i].numparams == 0)
@@ -3184,33 +3500,23 @@ gchar *expand_search_string(const gchar *search_string)
                                save_char = *cmd_end;
                                *cmd_end = '\0';
 
-                               new_len += strlen(cmd_start);
-
-                               /* do we need to add regexpcase ? */
-                               if (cmds[i].qualifier)
-                                       new_len += 10; /* "regexpcase " */
-
-                               if (term_char != '"')
-                                       new_len += 2;
-                               new_str = g_realloc(new_str, new_len);
-
                                if (cmds[i].qualifier) {
                                        if (casesens)
-                                               strcat(new_str, "regexp ");
+                                               g_string_append(matcherstr, "regexp ");
                                        else
-                                               strcat(new_str, "regexpcase ");
+                                               g_string_append(matcherstr, "regexpcase ");
                                }
 
                                /* do we need to add quotes ? */
                                if (cmds[i].quotes && term_char != '"')
-                                       strcat(new_str, "\"");
+                                       g_string_append(matcherstr, "\"");
 
                                /* copy actual parameter */
-                               strcat(new_str, cmd_start);
+                               g_string_append(matcherstr, cmd_start);
 
                                /* do we need to add quotes ? */
                                if (cmds[i].quotes && term_char != '"')
-                                       strcat(new_str, "\"");
+                                       g_string_append(matcherstr, "\"");
 
                                /* restore original character */
                                *cmd_end = save_char;
@@ -3226,6 +3532,33 @@ gchar *expand_search_string(const gchar *search_string)
        }
 
        g_free(copy_str);
-       return new_str;
+       returnstr = matcherstr->str;
+       g_string_free(matcherstr, FALSE);
+       return returnstr;
+}
+
+guint g_stricase_hash(gconstpointer gptr)
+{
+       guint hash_result = 0;
+       const char *str;
+
+       for (str = gptr; str && *str; str++) {
+               if (isupper(*str)) hash_result += (*str + ' ');
+               else hash_result += *str;
+       }
+
+       return hash_result;
 }
 
+gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
+{
+       const char *str1 = gptr1;
+       const char *str2 = gptr2;
+
+       return !strcasecmp(str1, str2);
+}
+
+gint g_int_compare(gconstpointer a, gconstpointer b)
+{
+       return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
+}