* src/imap.c
[claws.git] / src / utils.c
index 173900b690b9a8dd639ecbfa988eb86a42e81b2c..ff10b1d117724fe0748de3b73a80145756f2a182 100644 (file)
@@ -44,6 +44,7 @@
 
 #include "intl.h"
 #include "utils.h"
+#include "socket.h"
 #include "statusbar.h"
 #include "logwindow.h"
 
@@ -982,7 +983,7 @@ gboolean is_ascii_str(const guchar *str)
        return TRUE;
 }
 
-gint get_quote_level(const gchar *str)
+gint get_quote_level(const gchar *str, const gchar *quote_chars)
 {
        const gchar *first_pos;
        const gchar *last_pos;
@@ -990,11 +991,11 @@ gint get_quote_level(const gchar *str)
        gint quote_level = -1;
 
        /* speed up line processing by only searching to the last '>' */
-       if ((first_pos = strchr(str, '>')) != NULL) {
+       if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
                /* skip a line if it contains a '<' before the initial '>' */
                if (memchr(str, '<', first_pos - str) != NULL)
                        return -1;
-               last_pos = strrchr(first_pos, '>');
+               last_pos = line_has_quote_char_last(first_pos, quote_chars);
        } else
                return -1;
 
@@ -1006,14 +1007,16 @@ gint get_quote_level(const gchar *str)
                                break;
                }
 
-               if (*p == '>')
+               if (strchr(quote_chars, *p))
                        quote_level++;
                else if (*p != '-' && !isspace(*p) && p <= last_pos) {
                        /* any characters are allowed except '-' and space */
-                       while (*p != '-' && *p != '>' && !isspace(*p) &&
-                              p < last_pos)
+                       while (*p != '-' 
+                              && !strchr(quote_chars, *p) 
+                              && !isspace(*p) 
+                              && p < last_pos)
                                p++;
-                       if (*p == '>')
+                       if (strchr(quote_chars, *p))
                                quote_level++;
                        else
                                break;
@@ -1025,6 +1028,42 @@ gint get_quote_level(const gchar *str)
        return quote_level;
 }
 
+const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars) 
+{
+       gchar * position = NULL;
+       gchar * tmp_pos = NULL;
+       int i;
+
+       if (quote_chars == NULL)
+               return FALSE;
+       
+       for (i = 0; i < strlen(quote_chars); i++) {
+               tmp_pos = strchr (str,  quote_chars[i]);
+               if(position == NULL 
+                  || (tmp_pos != NULL && position >= tmp_pos) )
+                       position = tmp_pos;
+       }
+       return position; 
+}
+
+const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars) 
+{
+       gchar * position = NULL;
+       gchar * tmp_pos = NULL;
+       int i;
+
+       if (quote_chars == NULL)
+               return FALSE;
+       
+       for (i = 0; i < strlen(quote_chars); i++) {
+               tmp_pos = strrchr (str, quote_chars[i]);
+               if(position == NULL 
+                  || (tmp_pos != NULL && position <= tmp_pos) )
+                       position = tmp_pos;
+       }
+       return position; 
+}
+
 gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
 {
        register guint haystack_len, needle_len;
@@ -1062,13 +1101,100 @@ gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
        return NULL;
 }
 
-/* this fuction was taken from gstrfuncs.c in glib. */
+gchar *strchr_parenthesis_close(const gchar *str, gchar op, gchar cl)
+{
+       const gchar *p;
+       gchar quote_chr = '"';
+       gint in_brace;
+       gboolean in_quote = FALSE;
+
+       p = str;
+
+       if ((p = strchr_with_skip_quote(p, quote_chr, op))) {
+               p++;
+               in_brace = 1;
+               while (*p) {
+                       if (*p == op && !in_quote)
+                               in_brace++;
+                       else if (*p == cl && !in_quote)
+                               in_brace--;
+                       else if (*p == quote_chr)
+                               in_quote ^= TRUE;
+
+                       if (in_brace == 0)
+                               return (gchar *)p;
+
+                       p++;
+               }
+       }
+
+       return NULL;
+}
+
+gchar **strsplit_parenthesis(const gchar *str, gchar op, gchar cl,
+                            gint max_tokens)
+{
+       GSList *string_list = NULL, *slist;
+       gchar **str_array;
+       const gchar *s_op, *s_cl;
+       guint i, n = 1;
+
+       g_return_val_if_fail(str != NULL, NULL);
+
+       if (max_tokens < 1)
+               max_tokens = G_MAXINT;
+
+       s_op = strchr_with_skip_quote(str, '"', op);
+       if (!s_op) return NULL;
+       str = s_op;
+       s_cl = strchr_parenthesis_close(str, op, cl);
+       if (s_cl) {
+               do {
+                       guint len;
+                       gchar *new_string;
+
+                       str++;
+                       len = s_cl - str;
+                       new_string = g_new(gchar, len + 1);
+                       strncpy(new_string, str, len);
+                       new_string[len] = 0;
+                       string_list = g_slist_prepend(string_list, new_string);
+                       n++;
+                       str = s_cl + 1;
+
+                       while (*str && isspace(*str)) str++;
+                       if (*str != op) {
+                               string_list = g_slist_prepend(string_list,
+                                                             g_strdup(""));
+                               n++;
+                               s_op = strchr_with_skip_quote(str, '"', op);
+                               if (!--max_tokens || !s_op) break;
+                               str = s_op;
+                       } else
+                               s_op = str;
+                       s_cl = strchr_parenthesis_close(str, op, cl);
+               } while (--max_tokens && s_cl);
+       }
+
+       str_array = g_new(gchar*, n);
+
+       i = n - 1;
+
+       str_array[i--] = NULL;
+       for (slist = string_list; slist; slist = slist->next)
+               str_array[i--] = slist->data;
+
+       g_slist_free(string_list);
+
+       return str_array;
+}
+
 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
                            gint max_tokens)
 {
        GSList *string_list = NULL, *slist;
-       gchar **str_array, *s;
-       guint i, n = 1;
+       gchar **str_array, *s, *new_str;
+       guint i, n = 1, len;
 
        g_return_val_if_fail(str != NULL, NULL);
        g_return_val_if_fail(delim != NULL, NULL);
@@ -1081,13 +1207,15 @@ gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
                guint delimiter_len = strlen(delim);
 
                do {
-                       guint len;
-                       gchar *new_str;
-
                        len = s - str;
-                       new_str = g_new(gchar, len + 1);
-                       strncpy(new_str, str, len);
-                       new_str[len] = 0;
+                       new_str = g_strndup(str, len);
+
+                       if (new_str[0] == '\'' || new_str[0] == '\"') {
+                               if (new_str[len - 1] == new_str[0]) {
+                                       new_str[len - 1] = '\0';
+                                       memmove(new_str, new_str + 1, len - 1);
+                               }
+                       }
                        string_list = g_slist_prepend(string_list, new_str);
                        n++;
                        str = s + delimiter_len;
@@ -1096,8 +1224,16 @@ gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
        }
 
        if (*str) {
+               new_str = g_strdup(str);
+               if (new_str[0] == '\'' || new_str[0] == '\"') {
+                       len = strlen(str);
+                       if (new_str[len - 1] == new_str[0]) {
+                               new_str[len - 1] = '\0';
+                               memmove(new_str, new_str + 1, len - 1);
+                       }
+               }
+               string_list = g_slist_prepend(string_list, new_str);
                n++;
-               string_list = g_slist_prepend(string_list, g_strdup(str));
        }
 
        str_array = g_new(gchar*, n);
@@ -1113,6 +1249,58 @@ gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
        return str_array;
 }
 
+gchar *get_abbrev_newsgroup_name(const gchar *group)
+{
+       gchar *abbrev_group;
+       gchar *ap;
+       const gchar *p = group;
+
+       abbrev_group = ap = g_malloc(strlen(group) + 1);
+
+       while (*p) {
+               while (*p == '.')
+                       *ap++ = *p++;
+               if (strchr(p, '.')) {
+                       *ap++ = *p++;
+                       while (*p != '.') p++;
+               } else {
+                       strcpy(ap, p);
+                       return abbrev_group;
+               }
+       }
+
+       *ap = '\0';
+       return abbrev_group;
+}
+
+gchar *trim_string(const gchar *str, gint len)
+{
+       const gchar *p = str;
+       gint mb_len;
+       gchar *new_str;
+       gint new_len = 0;
+
+       if (!str) return NULL;
+       if (strlen(str) <= len)
+               return g_strdup(str);
+
+       while (*p != '\0') {
+               mb_len = mblen(p, MB_LEN_MAX);
+               if (mb_len == 0)
+                       break;
+               else if (mb_len < 0)
+                       return g_strdup(str);
+               else if (new_len + mb_len > len)
+                       break;
+               else
+                       new_len += mb_len;
+               p += mb_len;
+       }
+
+       Xstrndup_a(new_str, str, new_len, return g_strdup(str));
+       return g_strconcat(new_str, "...", NULL);
+}
+
 GList *uri_list_extract_filenames(const gchar *uri_list)
 {
        GList *result = NULL;
@@ -1161,6 +1349,63 @@ GList *uri_list_extract_filenames(const gchar *uri_list)
        } \
 }
 
+gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc,
+                    gchar **subject, gchar **body)
+{
+       gchar *tmp_mailto;
+       gchar *p;
+
+       Xstrdup_a(tmp_mailto, mailto, return -1);
+
+       if (!strncmp(tmp_mailto, "mailto:", 7))
+               tmp_mailto += 7;
+
+       p = strchr(tmp_mailto, '?');
+       if (p) {
+               *p = '\0';
+               p++;
+       }
+
+       if (to && !*to)
+               *to = g_strdup(tmp_mailto);
+
+       while (p) {
+               gchar *field, *value;
+
+               field = p;
+
+               p = strchr(p, '=');
+               if (!p) break;
+               *p = '\0';
+               p++;
+
+               value = p;
+
+               p = strchr(p, '&');
+               if (p) {
+                       *p = '\0';
+                       p++;
+               }
+
+               if (*value == '\0') continue;
+
+               if (cc && !*cc && !g_strcasecmp(field, "cc")) {
+                       *cc = g_strdup(value);
+               } else if (bcc && !*bcc && !g_strcasecmp(field, "bcc")) {
+                       *bcc = g_strdup(value);
+               } else if (subject && !*subject &&
+                          !g_strcasecmp(field, "subject")) {
+                       *subject = g_malloc(strlen(value) + 1);
+                       decode_uri(*subject, value);
+               } else if (body && !*body && !g_strcasecmp(field, "body")) {
+                       *body = g_malloc(strlen(value) + 1);
+                       decode_uri(*body, value);
+               }
+       }
+
+       return 0;
+}
+
 /*
  * We need this wrapper around g_get_home_dir(), so that
  * we can fix some Windoze things here.  Should be done in glibc of course
@@ -1258,13 +1503,35 @@ gchar *get_template_dir(void)
        return template_dir;
 }
 
+gchar *get_header_cache_dir(void)
+{
+       static gchar *header_dir = NULL;
+
+       if (!header_dir)
+               header_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                                        HEADER_CACHE_DIR, NULL);
+
+       return header_dir;
+}
+
+gchar *get_tmp_dir(void)
+{
+       static gchar *tmp_dir = NULL;
+
+       if (!tmp_dir)
+               tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                                     TMP_DIR, NULL);
+
+       return tmp_dir;
+}
+
 gchar *get_tmp_file(void)
 {
-       static gchar *tmp_file = NULL;
+       gchar *tmp_file;
+       static guint32 id = 0;
 
-       if (!tmp_file)
-               tmp_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
-                                      "tmpfile", NULL);
+       tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
+                                  get_tmp_dir(), G_DIR_SEPARATOR, id++);
 
        return tmp_file;
 }
@@ -1274,14 +1541,23 @@ gchar *get_domain_name(void)
        static gchar *domain_name = NULL;
 
        if (!domain_name) {
-               gchar buf[BUFFSIZE] = "";
+               gchar buf[128] = "";
+               struct hostent *hp;
 
                if (gethostname(buf, sizeof(buf)) < 0) {
                        perror("gethostname");
-                       strcpy(buf, "unknown");
+                       domain_name = "unknown";
+               } else {
+                       buf[sizeof(buf) - 1] = '\0';
+                       if ((hp = my_gethostbyname(buf)) == NULL) {
+                               perror("gethostbyname");
+                               domain_name = g_strdup(buf);
+                       } else {
+                               domain_name = g_strdup(hp->h_name);
+                       }
                }
 
-               domain_name = g_strdup(buf);
+               debug_print("domain name = %s\n", domain_name);
        }
 
        return domain_name;
@@ -1299,6 +1575,32 @@ off_t get_file_size(const gchar *file)
        return s.st_size;
 }
 
+off_t get_file_size_as_crlf(const gchar *file)
+{
+       FILE *fp;
+       off_t size = 0;
+       gchar buf[BUFFSIZE];
+
+       if ((fp = fopen(file, "rb")) == NULL) {
+               FILE_OP_ERROR(file, "fopen");
+               return -1;
+       }
+
+       while (fgets(buf, sizeof(buf), fp) != NULL) {
+               strretchomp(buf);
+               size += strlen(buf) + 2;
+       }
+
+       if (ferror(fp)) {
+               FILE_OP_ERROR(file, "fgets");
+               size = -1;
+       }
+
+       fclose(fp);
+
+       return size;
+}
+
 off_t get_left_file_size(FILE *fp)
 {
        glong pos;
@@ -1330,6 +1632,9 @@ gboolean file_exist(const gchar *file, gboolean allow_fifo)
 {
        struct stat s;
 
+       if (file == NULL)
+               return FALSE;
+
        if (stat(file, &s) < 0) {
                if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
                return FALSE;
@@ -1345,6 +1650,9 @@ gboolean is_dir_exist(const gchar *dir)
 {
        struct stat s;
 
+       if (dir == NULL)
+               return FALSE;
+
        if (stat(dir, &s) < 0) {
                if (ENOENT != errno) FILE_OP_ERROR(dir, "stat");
                return FALSE;
@@ -1356,6 +1664,21 @@ gboolean is_dir_exist(const gchar *dir)
        return FALSE;
 }
 
+gboolean is_file_entry_exist(const gchar *file)
+{
+       struct stat s;
+
+       if (file == NULL)
+               return FALSE;
+
+       if (stat(file, &s) < 0) {
+               if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
 gint change_dir(const gchar *dir)
 {
        gchar *prevdir = NULL;
@@ -1380,6 +1703,18 @@ gint change_dir(const gchar *dir)
        return 0;
 }
 
+gint make_dir(const gchar *dir)
+{
+       if (mkdir(dir, S_IRWXU) < 0) {
+               FILE_OP_ERROR(dir, "mkdir");
+               return -1;
+       }
+       if (chmod(dir, S_IRWXU) < 0)
+               FILE_OP_ERROR(dir, "chmod");
+
+       return 0;
+}
+
 gint make_dir_hier(const gchar *dir)
 {
        gchar *parent_dir;
@@ -1389,24 +1724,18 @@ gint make_dir_hier(const gchar *dir)
                parent_dir = g_strndup(dir, p - dir);
                if (*parent_dir != '\0') {
                        if (!is_dir_exist(parent_dir)) {
-                               if (mkdir(parent_dir, S_IRWXU) < 0) {
-                                       FILE_OP_ERROR(parent_dir, "mkdir");
+                               if (make_dir(parent_dir) < 0) {
                                        g_free(parent_dir);
                                        return -1;
                                }
-                               if (chmod(parent_dir, S_IRWXU) < 0)
-                                       FILE_OP_ERROR(parent_dir, "chmod");
                        }
                }
                g_free(parent_dir);
        }
+
        if (!is_dir_exist(dir)) {
-               if (mkdir(dir, S_IRWXU) < 0) {
-                       FILE_OP_ERROR(dir, "mkdir");
+               if (make_dir(dir) < 0)
                        return -1;
-               }
-               if (chmod(dir, S_IRWXU) < 0)
-                       FILE_OP_ERROR(dir, "chmod");
        }
 
        return 0;
@@ -1422,11 +1751,13 @@ gint remove_all_files(const gchar *dir)
 
        if (chdir(dir) < 0) {
                FILE_OP_ERROR(dir, "chdir");
+               g_free(prev_dir);
                return -1;
        }
 
        if ((dp = opendir(".")) == NULL) {
                FILE_OP_ERROR(dir, "opendir");
+               g_free(prev_dir);
                return -1;
        }
 
@@ -1463,17 +1794,66 @@ gint remove_numbered_files(const gchar *dir, guint first, guint last)
 
        if (chdir(dir) < 0) {
                FILE_OP_ERROR(dir, "chdir");
+               g_free(prev_dir);
                return -1;
        }
 
        if ((dp = opendir(".")) == NULL) {
                FILE_OP_ERROR(dir, "opendir");
+               g_free(prev_dir);
                return -1;
        }
 
        while ((d = readdir(dp)) != NULL) {
                fileno = to_number(d->d_name);
                if (fileno >= 0 && first <= fileno && fileno <= last) {
+                       if (is_dir_exist(d->d_name))
+                               continue;
+                       if (unlink(d->d_name) < 0)
+                               FILE_OP_ERROR(d->d_name, "unlink");
+               }
+       }
+
+       closedir(dp);
+
+       if (chdir(prev_dir) < 0) {
+               FILE_OP_ERROR(prev_dir, "chdir");
+               g_free(prev_dir);
+               return -1;
+       }
+
+       g_free(prev_dir);
+
+       return 0;
+}
+
+gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
+{
+       DIR *dp;
+       struct dirent *d;
+       gchar *prev_dir;
+       gint fileno;
+
+       prev_dir = g_get_current_dir();
+
+       if (chdir(dir) < 0) {
+               FILE_OP_ERROR(dir, "chdir");
+               g_free(prev_dir);
+               return -1;
+       }
+
+       if ((dp = opendir(".")) == NULL) {
+               FILE_OP_ERROR(dir, "opendir");
+               g_free(prev_dir);
+               return -1;
+       }
+
+       while ((d = readdir(dp)) != NULL) {
+               fileno = to_number(d->d_name);
+               if (fileno >= 0 && (g_slist_find(numberlist, GINT_TO_POINTER(fileno)) == NULL)) {
+                       debug_print("removing unwanted file %d from %s\n", fileno, dir);
+                       if (is_dir_exist(d->d_name))
+                               continue;
                        if (unlink(d->d_name) < 0)
                                FILE_OP_ERROR(d->d_name, "unlink");
                }
@@ -1497,6 +1877,62 @@ gint remove_all_numbered_files(const gchar *dir)
        return remove_numbered_files(dir, 0, UINT_MAX);
 }
 
+gint remove_expired_files(const gchar *dir, guint hours)
+{
+       DIR *dp;
+       struct dirent *d;
+       struct stat s;
+       gchar *prev_dir;
+       gint fileno;
+       time_t mtime, now, expire_time;
+
+       prev_dir = g_get_current_dir();
+
+       if (chdir(dir) < 0) {
+               FILE_OP_ERROR(dir, "chdir");
+               g_free(prev_dir);
+               return -1;
+       }
+
+       if ((dp = opendir(".")) == NULL) {
+               FILE_OP_ERROR(dir, "opendir");
+               g_free(prev_dir);
+               return -1;
+       }
+
+       now = time(NULL);
+       expire_time = hours * 60 * 60;
+
+       while ((d = readdir(dp)) != NULL) {
+               fileno = to_number(d->d_name);
+               if (fileno >= 0) {
+                       if (stat(d->d_name, &s) < 0) {
+                               FILE_OP_ERROR(d->d_name, "stat");
+                               continue;
+                       }
+                       if (S_ISDIR(s.st_mode))
+                               continue;
+                       mtime = MAX(s.st_mtime, s.st_atime);
+                       if (now - mtime > expire_time) {
+                               if (unlink(d->d_name) < 0)
+                                       FILE_OP_ERROR(d->d_name, "unlink");
+                       }
+               }
+       }
+
+       closedir(dp);
+
+       if (chdir(prev_dir) < 0) {
+               FILE_OP_ERROR(prev_dir, "chdir");
+               g_free(prev_dir);
+               return -1;
+       }
+
+       g_free(prev_dir);
+
+       return 0;
+}
+
 gint remove_dir_recursive(const gchar *dir)
 {
        struct stat s;
@@ -1624,49 +2060,108 @@ gint copy_file(const gchar *src, const gchar *dest)
                return -1;
        }
 
-       while ((n_read = read(src_fd, buf, sizeof(buf))) > 0) {
-               gint len = n_read;
-               gchar *bufp = buf;
-
-               while (len > 0) {
-                       n_write = write(dest_fd, bufp, len);
-                       if (n_write <= 0) {
-                               g_warning(_("writing to %s failed.\n"), dest);
-                               close(dest_fd);
-                               close(src_fd);
-                               unlink(dest);
-                               if (dest_bak) {
-                                       if (rename(dest_bak, dest) < 0)
-                                               FILE_OP_ERROR(dest_bak, "rename");
-                                       g_free(dest_bak);
-                               }
-                               return -1;
-                       }
-                       len -= n_write;
-                       bufp += n_write;
+       while ((n_read = read(src_fd, buf, sizeof(buf))) > 0) {
+               gint len = n_read;
+               gchar *bufp = buf;
+
+               while (len > 0) {
+                       n_write = write(dest_fd, bufp, len);
+                       if (n_write <= 0) {
+                               g_warning(_("writing to %s failed.\n"), dest);
+                               close(dest_fd);
+                               close(src_fd);
+                               unlink(dest);
+                               if (dest_bak) {
+                                       if (rename(dest_bak, dest) < 0)
+                                               FILE_OP_ERROR(dest_bak, "rename");
+                                       g_free(dest_bak);
+                               }
+                               return -1;
+                       }
+                       len -= n_write;
+                       bufp += n_write;
+               }
+       }
+
+       close(src_fd);
+       close(dest_fd);
+
+       if (n_read < 0 || get_file_size(src) != get_file_size(dest)) {
+               g_warning(_("File copy from %s to %s failed.\n"), src, dest);
+               unlink(dest);
+               if (dest_bak) {
+                       if (rename(dest_bak, dest) < 0)
+                               FILE_OP_ERROR(dest_bak, "rename");
+                       g_free(dest_bak);
+               }
+               return -1;
+       }
+       g_free(dest_bak);
+
+       return 0;
+}
+#endif
+
+
+/*
+ * Append src file body to the tail of dest file.
+ * Now keep_backup has no effects.
+ */
+gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
+{
+       FILE *src_fp, *dest_fp;
+       gint n_read;
+       gchar buf[BUFSIZ];
+
+       gboolean err = FALSE;
+
+       if ((src_fp = fopen(src, "rb")) == NULL) {
+               FILE_OP_ERROR(src, "fopen");
+               return -1;
+       }
+       
+       if ((dest_fp = fopen(dest, "ab")) == 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 ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
+               if (n_read < sizeof(buf) && ferror(src_fp))
+                       break;
+               if (fwrite(buf, n_read, 1, dest_fp) < 1) {
+                       g_warning(_("writing to %s failed.\n"), dest);
+                       fclose(dest_fp);
+                       fclose(src_fp);
+                       unlink(dest);
+                       return -1;
                }
        }
 
-       close(src_fd);
-       close(dest_fd);
+       if (ferror(src_fp)) {
+               FILE_OP_ERROR(src, "fread");
+               err = TRUE;
+       }
+       fclose(src_fp);
+       if (fclose(dest_fp) == EOF) {
+               FILE_OP_ERROR(dest, "fclose");
+               err = TRUE;
+       }
 
-       if (n_read < 0 || get_file_size(src) != get_file_size(dest)) {
-               g_warning(_("File copy from %s to %s failed.\n"), src, dest);
+       if (err) {
                unlink(dest);
-               if (dest_bak) {
-                       if (rename(dest_bak, dest) < 0)
-                               FILE_OP_ERROR(dest_bak, "rename");
-                       g_free(dest_bak);
-               }
                return -1;
        }
-       g_free(dest_bak);
 
        return 0;
 }
-#endif
 
-gint copy_file(const gchar *src, const gchar *dest)
+gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
 {
        FILE *src_fp, *dest_fp;
        gint n_read;
@@ -1674,7 +2169,7 @@ gint copy_file(const gchar *src, const gchar *dest)
        gchar *dest_bak = NULL;
        gboolean err = FALSE;
 
-       if ((src_fp = fopen(src, "r")) == NULL) {
+       if ((src_fp = fopen(src, "rb")) == NULL) {
                FILE_OP_ERROR(src, "fopen");
                return -1;
        }
@@ -1688,7 +2183,7 @@ gint copy_file(const gchar *src, const gchar *dest)
                }
        }
 
-       if ((dest_fp = fopen(dest, "w")) == NULL) {
+       if ((dest_fp = fopen(dest, "wb")) == NULL) {
                FILE_OP_ERROR(dest, "fopen");
                fclose(src_fp);
                if (dest_bak) {
@@ -1741,15 +2236,18 @@ gint copy_file(const gchar *src, const gchar *dest)
                return -1;
        }
 
+       if (keep_backup == FALSE && dest_bak)
+               unlink(dest_bak);
+
        g_free(dest_bak);
 
        return 0;
 }
 
-gint move_file(const gchar *src, const gchar *dest)
+gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
 {
-       if (is_file_exist(dest)) {
-               g_warning(_("move_file(): file %s already exists."), dest);
+       if (overwrite == FALSE && is_file_exist(dest)) {
+               g_warning("move_file(): file %s already exists.", dest);
                return -1;
        }
 
@@ -1760,13 +2258,174 @@ gint move_file(const gchar *src, const gchar *dest)
                return -1;
        }
 
-       if (copy_file(src, dest) < 0) return -1;
+       if (copy_file(src, dest, FALSE) < 0) return -1;
 
        unlink(src);
 
        return 0;
 }
 
+gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
+{
+       FILE *dest_fp;
+       gint n_read;
+       gint bytes_left, to_read;
+       gchar buf[BUFSIZ];
+       gboolean err = FALSE;
+
+       if (fseek(fp, offset, SEEK_SET) < 0) {
+               perror("fseek");
+               return -1;
+       }
+
+       if ((dest_fp = fopen(dest, "wb")) == NULL) {
+               FILE_OP_ERROR(dest, "fopen");
+               return -1;
+       }
+
+       if (change_file_mode_rw(dest_fp, dest) < 0) {
+               FILE_OP_ERROR(dest, "chmod");
+               g_warning("can't change file mode\n");
+       }
+
+       bytes_left = length;
+       to_read = MIN(bytes_left, sizeof(buf));
+
+       while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
+               if (n_read < to_read && ferror(fp))
+                       break;
+               if (fwrite(buf, n_read, 1, dest_fp) < 1) {
+                       g_warning(_("writing to %s failed.\n"), dest);
+                       fclose(dest_fp);
+                       unlink(dest);
+                       return -1;
+               }
+               bytes_left -= n_read;
+               if (bytes_left == 0)
+                       break;
+               to_read = MIN(bytes_left, sizeof(buf));
+       }
+
+       if (ferror(fp)) {
+               perror("fread");
+               err = TRUE;
+       }
+       if (fclose(dest_fp) == EOF) {
+               FILE_OP_ERROR(dest, "fclose");
+               err = TRUE;
+       }
+
+       if (err) {
+               unlink(dest);
+               return -1;
+       }
+
+       return 0;
+}
+
+/* convert line endings into CRLF. If the last line doesn't end with
+ * linebreak, add it.
+ */
+gint canonicalize_file(const gchar *src, const gchar *dest)
+{
+       FILE *src_fp, *dest_fp;
+       gchar buf[BUFFSIZE];
+       gint len;
+       gboolean err = FALSE;
+       gboolean last_linebreak = 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) {
+               gint r = 0;
+
+               len = strlen(buf);
+               if (len == 0) break;
+               last_linebreak = FALSE;
+
+               if (buf[len - 1] != '\n') {
+                       last_linebreak = TRUE;
+                       r = fputs(buf, dest_fp);
+               } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
+                       r = fputs(buf, dest_fp);
+               } else {
+                       if (len > 1) {
+                               r = fwrite(buf, len - 1, 1, dest_fp);
+                               if (r != 1)
+                                       r = EOF;
+                       }
+                       if (r != EOF)
+                               r = fputs("\r\n", dest_fp);
+               }
+
+               if (r == EOF) {
+                       g_warning("writing to %s failed.\n", dest);
+                       fclose(dest_fp);
+                       fclose(src_fp);
+                       unlink(dest);
+                       return -1;
+               }
+       }
+
+       if (last_linebreak == TRUE) {
+               if (fputs("\r\n", dest_fp) == EOF)
+                       err = TRUE;
+       }
+
+       if (ferror(src_fp)) {
+               FILE_OP_ERROR(src, "fread");
+               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 canonicalize_file_replace(const gchar *file)
+{
+       gchar *tmp_file;
+
+       tmp_file = get_tmp_file();
+
+       if (canonicalize_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;
+}
+
 gint change_file_mode_rw(FILE *fp, const gchar *file)
 {
 #if HAVE_FCHMOD
@@ -1788,7 +2447,7 @@ FILE *my_tmpfile(void)
        gint fd;
        FILE *fp;
 
-       tmpdir = g_get_tmp_dir();
+       tmpdir = get_tmp_dir();
        tmplen = strlen(tmpdir);
        progname = g_get_prgname();
        proglen = strlen(progname);
@@ -1816,6 +2475,107 @@ FILE *my_tmpfile(void)
        return tmpfile();
 }
 
+FILE *str_open_as_stream(const gchar *str)
+{
+       FILE *fp;
+       size_t len;
+
+       g_return_val_if_fail(str != NULL, NULL);
+
+       fp = my_tmpfile();
+       if (!fp) {
+               FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
+               return NULL;
+       }
+
+       len = strlen(str);
+       if (len == 0) return fp;
+
+       if (fwrite(str, len, 1, fp) != 1) {
+               FILE_OP_ERROR("str_open_as_stream", "fwrite");
+               fclose(fp);
+               return NULL;
+       }
+
+       rewind(fp);
+       return fp;
+}
+
+gint str_write_to_file(const gchar *str, const gchar *file)
+{
+       FILE *fp;
+       size_t len;
+
+       g_return_val_if_fail(str != NULL, -1);
+       g_return_val_if_fail(file != NULL, -1);
+
+       if ((fp = fopen(file, "wb")) == NULL) {
+               FILE_OP_ERROR(file, "fopen");
+               return -1;
+       }
+
+       len = strlen(str);
+       if (len == 0) {
+               fclose(fp);
+               return 0;
+       }
+
+       if (fwrite(str, len, 1, fp) != 1) {
+               FILE_OP_ERROR(file, "fwrite");
+               fclose(fp);
+               unlink(file);
+               return -1;
+       }
+
+       if (fclose(fp) == EOF) {
+               FILE_OP_ERROR(file, "fclose");
+               unlink(file);
+               return -1;
+       }
+
+       return 0;
+}
+
+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);
+
+       if ((fp = fopen(file, "rb")) == NULL) {
+               FILE_OP_ERROR(file, "fopen");
+               return NULL;
+       }
+
+       array = g_byte_array_new();
+
+       while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
+               if (n_read < sizeof(buf) && ferror(fp))
+                       break;
+               g_byte_array_append(array, buf, n_read);
+       }
+
+       if (ferror(fp)) {
+               FILE_OP_ERROR(file, "fread");
+               fclose(fp);
+               g_byte_array_free(array, TRUE);
+               return NULL;
+       }
+
+       fclose(fp);
+
+       buf[0] = '\0';
+       g_byte_array_append(array, buf, 1);
+       str = (gchar *)array->data;
+       g_byte_array_free(array, FALSE);
+
+       return str;
+}
+
 gint execute_async(gchar *const argv[])
 {
        pid_t pid;
@@ -1872,25 +2632,10 @@ gint execute_sync(gchar *const argv[])
 gint execute_command_line(const gchar *cmdline, gboolean async)
 {
        gchar **argv;
-       gint i;
        gint ret;
 
        argv = strsplit_with_quote(cmdline, " ", 0);
 
-       for (i = 0; argv[i] != NULL; i++) {
-               gchar *str = argv[i];
-
-               if (str[0] == '\'' || str[0] == '\"') {
-                       gint len;
-
-                       len = strlen(str);
-                       if (str[len - 1] == str[0]) {
-                               str[len - 1] = '\0';
-                               memmove(str, str + 1, len - 1);
-                       }
-               }
-       }
-
        if (async)
                ret = execute_async(argv);
        else
@@ -2185,7 +2930,7 @@ static FILE *log_fp = NULL;
 void set_log_file(const gchar *filename)
 {
        if (log_fp) return;
-       log_fp = fopen(filename, "w");
+       log_fp = fopen(filename, "wb");
        if (!log_fp)
                FILE_OP_ERROR(filename, "fopen");
 }
@@ -2222,66 +2967,74 @@ void debug_print_real(const gchar *format, ...)
        fputs(buf, stdout);
 }
 
+#define TIME_LEN       11
+
 void log_print(const gchar *format, ...)
 {
        va_list args;
-       gchar buf[BUFFSIZE];
-       gchar *logbuf;
-       gchar timestr[6];
+       gchar buf[BUFFSIZE + TIME_LEN];
        time_t t;
 
+       time(&t);
+       strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t));
+
        va_start(args, format);
-       g_vsnprintf(buf, sizeof(buf), format, args);
+       g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args);
        va_end(args);
-       
-       time(&t);
-       strftime(timestr, 6, "%H:%M", localtime(&t));
-       logbuf = g_strdup_printf("[%s] %s", timestr, buf);
 
-       if (debug_mode) fputs(logbuf, stdout);
-       log_window_append(logbuf, LOG_NORMAL);
+       if (debug_mode) fputs(buf, stdout);
+       log_window_append(buf, LOG_NORMAL);
        if (log_fp) {
-               fputs(logbuf, log_fp);
+               fputs(buf, log_fp);
                fflush(log_fp);
        }
        if (log_verbosity_count)
-               statusbar_puts_all(buf);
-       g_free(logbuf);
+               statusbar_puts_all(buf + TIME_LEN);
 }
 
 void log_message(const gchar *format, ...)
 {
        va_list args;
-       gchar buf[BUFFSIZE];
+       gchar buf[BUFFSIZE + TIME_LEN];
+       time_t t;
+
+       time(&t);
+       strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t));
 
        va_start(args, format);
-       g_vsnprintf(buf, sizeof(buf), format, args);
+       g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args);
        va_end(args);
 
-       if (debug_mode) g_message("%s", buf);
-       log_window_append(buf, LOG_MSG);
+       if (debug_mode) g_message("%s", buf + TIME_LEN);
+       log_window_append(buf + TIME_LEN, LOG_MSG);
        if (log_fp) {
-               fputs("message: ", log_fp);
-               fputs(buf, log_fp);
+               fwrite(buf, TIME_LEN, 1, log_fp);
+               fputs("* message: ", log_fp);
+               fputs(buf + TIME_LEN, log_fp);
                fflush(log_fp);
        }
-       statusbar_puts_all(buf);
+       statusbar_puts_all(buf + TIME_LEN);
 }
 
 void log_warning(const gchar *format, ...)
 {
        va_list args;
-       gchar buf[BUFFSIZE];
+       gchar buf[BUFFSIZE + TIME_LEN];
+       time_t t;
+
+       time(&t);
+       strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t));
 
        va_start(args, format);
-       g_vsnprintf(buf, sizeof(buf), format, args);
+       g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args);
        va_end(args);
 
        g_warning("%s", buf);
-       log_window_append(buf, LOG_WARN);
+       log_window_append(buf + TIME_LEN, LOG_WARN);
        if (log_fp) {
-               fputs("*** warning: ", log_fp);
-               fputs(buf, log_fp);
+               fwrite(buf, TIME_LEN, 1, log_fp);
+               fputs("** warning: ", log_fp);
+               fputs(buf + TIME_LEN, log_fp);
                fflush(log_fp);
        }
 }
@@ -2289,17 +3042,22 @@ void log_warning(const gchar *format, ...)
 void log_error(const gchar *format, ...)
 {
        va_list args;
-       gchar buf[BUFFSIZE];
+       gchar buf[BUFFSIZE + TIME_LEN];
+       time_t t;
+
+       time(&t);
+       strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t));
 
        va_start(args, format);
-       g_vsnprintf(buf, sizeof(buf), format, args);
+       g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args);
        va_end(args);
 
        g_warning("%s", buf);
-       log_window_append(buf, LOG_ERROR);
+       log_window_append(buf + TIME_LEN, LOG_ERROR);
        if (log_fp) {
+               fwrite(buf, TIME_LEN, 1, log_fp);
                fputs("*** error: ", log_fp);
-               fputs(buf, log_fp);
+               fputs(buf + TIME_LEN, log_fp);
                fflush(log_fp);
        }
 }
@@ -2353,3 +3111,13 @@ gboolean subject_is_reply(const gchar *subject)
        if (subject == NULL) return FALSE;
        else return 0 == g_strncasecmp(subject, "Re: ", 4);
 }
+
+FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
+{
+       int fd;
+       
+       *filename = g_strdup_printf("%s%csylpheed.XXXXXX", dir, G_DIR_SEPARATOR);
+       fd = mkstemp(*filename);
+
+       return fdopen(fd, "w+");
+}