fix a format warning, thanks to Daniel Jakots
[claws.git] / src / common / utils.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2015 Hiroyuki Yamamoto & The Claws Mail Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * The code of the g_utf8_substring function below is owned by
19  * Matthias Clasen <matthiasc@src.gnome.org>/<mclasen@redhat.com>
20  * and is got from GLIB 2.30: https://git.gnome.org/browse/glib/commit/
21  *  ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
22  *
23  * GLib 2.30 is licensed under GPL v2 or later and:
24  * Copyright (C) 1999 Tom Tromey
25  * Copyright (C) 2000 Red Hat, Inc.
26  *
27  * https://git.gnome.org/browse/glib/tree/glib/gutf8.c
28  *  ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
29  */
30
31 #ifdef HAVE_CONFIG_H
32 #  include "config.h"
33 #include "claws-features.h"
34 #endif
35
36 #include "defs.h"
37
38 #include <glib.h>
39 #include <gio/gio.h>
40
41 #include <glib/gi18n.h>
42
43 #ifdef USE_PTHREAD
44 #include <pthread.h>
45 #endif
46
47 #include <stdio.h>
48 #include <string.h>
49 #include <ctype.h>
50 #include <errno.h>
51 #include <sys/param.h>
52 #include <sys/socket.h>
53
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
55 #  include <wchar.h>
56 #  include <wctype.h>
57 #endif
58 #include <stdlib.h>
59 #include <sys/stat.h>
60 #include <unistd.h>
61 #include <stdarg.h>
62 #include <sys/types.h>
63 #if HAVE_SYS_WAIT_H
64 #  include <sys/wait.h>
65 #endif
66 #include <dirent.h>
67 #include <time.h>
68 #include <regex.h>
69
70 #ifdef G_OS_UNIX
71 #include <sys/utsname.h>
72 #endif
73
74 #include <fcntl.h>
75
76 #ifdef G_OS_WIN32
77 #  include <direct.h>
78 #  include <io.h>
79 #  include <w32lib.h>
80 #endif
81
82 #include "utils.h"
83 #include "socket.h"
84 #include "../codeconv.h"
85
86 #define BUFFSIZE        8192
87
88 static gboolean debug_mode = FALSE;
89 #ifdef G_OS_WIN32
90 static GSList *tempfiles=NULL;
91 #endif
92
93 #if !GLIB_CHECK_VERSION(2, 26, 0)
94 guchar *g_base64_decode_wa(const gchar *text, gsize *out_len)
95 {
96         guchar *ret;
97         gsize input_length;
98         gint state = 0;
99         guint save = 0;
100
101         input_length = strlen(text);
102
103         ret = g_malloc0((input_length / 4) * 3 + 1);
104
105         *out_len = g_base64_decode_step(text, input_length, ret, &state, &save);
106
107         return ret;
108 }
109 #endif
110
111 /* Return true if we are running as root.  This function should beused
112    instead of getuid () == 0.  */
113 gboolean superuser_p (void)
114 {
115 #ifdef G_OS_WIN32
116   return w32_is_administrator ();
117 #else
118   return !getuid();
119 #endif  
120 }
121
122 GSList *slist_copy_deep(GSList *list, GCopyFunc func)
123 {
124 #if GLIB_CHECK_VERSION(2, 34, 0)
125         return g_slist_copy_deep(list, func, NULL);
126 #else
127         GSList *res = g_slist_copy(list);
128         GSList *walk = res;
129         while (walk) {
130                 walk->data = func(walk->data, NULL);
131                 walk = walk->next;
132         }
133         return res;
134 #endif
135 }
136
137 void list_free_strings(GList *list)
138 {
139         list = g_list_first(list);
140
141         while (list != NULL) {
142                 g_free(list->data);
143                 list = list->next;
144         }
145 }
146
147 void slist_free_strings(GSList *list)
148 {
149         while (list != NULL) {
150                 g_free(list->data);
151                 list = list->next;
152         }
153 }
154
155 void slist_free_strings_full(GSList *list)
156 {
157 #if GLIB_CHECK_VERSION(2,28,0)
158         g_slist_free_full(list, (GDestroyNotify)g_free);
159 #else
160         g_slist_foreach(list, (GFunc)g_free, NULL);
161         g_slist_free(list);
162 #endif
163 }
164
165 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
166 {
167         g_free(key);
168 }
169
170 void hash_free_strings(GHashTable *table)
171 {
172         g_hash_table_foreach(table, hash_free_strings_func, NULL);
173 }
174
175 gint str_case_equal(gconstpointer v, gconstpointer v2)
176 {
177         return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
178 }
179
180 guint str_case_hash(gconstpointer key)
181 {
182         const gchar *p = key;
183         guint h = *p;
184
185         if (h) {
186                 h = g_ascii_tolower(h);
187                 for (p += 1; *p != '\0'; p++)
188                         h = (h << 5) - h + g_ascii_tolower(*p);
189         }
190
191         return h;
192 }
193
194 void ptr_array_free_strings(GPtrArray *array)
195 {
196         gint i;
197         gchar *str;
198
199         cm_return_if_fail(array != NULL);
200
201         for (i = 0; i < array->len; i++) {
202                 str = g_ptr_array_index(array, i);
203                 g_free(str);
204         }
205 }
206
207 gint to_number(const gchar *nstr)
208 {
209         register const gchar *p;
210
211         if (*nstr == '\0') return -1;
212
213         for (p = nstr; *p != '\0'; p++)
214                 if (!g_ascii_isdigit(*p)) return -1;
215
216         return atoi(nstr);
217 }
218
219 /* convert integer into string,
220    nstr must be not lower than 11 characters length */
221 gchar *itos_buf(gchar *nstr, gint n)
222 {
223         g_snprintf(nstr, 11, "%d", n);
224         return nstr;
225 }
226
227 /* convert integer into string */
228 gchar *itos(gint n)
229 {
230         static gchar nstr[11];
231
232         return itos_buf(nstr, n);
233 }
234
235 #define divide(num,divisor,i,d)         \
236 {                                       \
237         i = num >> divisor;             \
238         d = num & ((1<<divisor)-1);     \
239         d = (d*100) >> divisor;         \
240 }
241
242
243 /*!
244  * \brief Convert a given size in bytes in a human-readable string
245  *
246  * \param size  The size expressed in bytes to convert in string
247  * \return      The string that respresents the size in an human-readable way
248  */
249 gchar *to_human_readable(goffset size)
250 {
251         static gchar str[14];
252         static gchar *b_format = NULL, *kb_format = NULL, 
253                      *mb_format = NULL, *gb_format = NULL;
254         register int t = 0, r = 0;
255         if (b_format == NULL) {
256                 b_format  = _("%dB");
257                 kb_format = _("%d.%02dKB");
258                 mb_format = _("%d.%02dMB");
259                 gb_format = _("%.2fGB");
260         }
261         
262         if (size < (goffset)1024) {
263                 g_snprintf(str, sizeof(str), b_format, (gint)size);
264                 return str;
265         } else if (size >> 10 < (goffset)1024) {
266                 divide(size, 10, t, r);
267                 g_snprintf(str, sizeof(str), kb_format, t, r);
268                 return str;
269         } else if (size >> 20 < (goffset)1024) {
270                 divide(size, 20, t, r);
271                 g_snprintf(str, sizeof(str), mb_format, t, r);
272                 return str;
273         } else {
274                 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
275                 return str;
276         }
277 }
278
279 /* strcmp with NULL-checking */
280 gint strcmp2(const gchar *s1, const gchar *s2)
281 {
282         if (s1 == NULL || s2 == NULL)
283                 return -1;
284         else
285                 return strcmp(s1, s2);
286 }
287 /* strstr with NULL-checking */
288 gchar *strstr2(const gchar *s1, const gchar *s2)
289 {
290         if (s1 == NULL || s2 == NULL)
291                 return NULL;
292         else
293                 return strstr(s1, s2);
294 }
295 /* compare paths */
296 gint path_cmp(const gchar *s1, const gchar *s2)
297 {
298         gint len1, len2;
299         int rc;
300 #ifdef G_OS_WIN32
301         gchar *s1buf, *s2buf;
302 #endif
303
304         if (s1 == NULL || s2 == NULL) return -1;
305         if (*s1 == '\0' || *s2 == '\0') return -1;
306
307 #ifdef G_OS_WIN32
308         s1buf = g_strdup (s1);
309         s2buf = g_strdup (s2);
310         subst_char (s1buf, '/', G_DIR_SEPARATOR);
311         subst_char (s2buf, '/', G_DIR_SEPARATOR);
312         s1 = s1buf;
313         s2 = s2buf;
314 #endif /* !G_OS_WIN32 */
315
316         len1 = strlen(s1);
317         len2 = strlen(s2);
318
319         if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
320         if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
321
322         rc = strncmp(s1, s2, MAX(len1, len2));
323 #ifdef G_OS_WIN32
324         g_free (s1buf);
325         g_free (s2buf);
326 #endif /* !G_OS_WIN32 */
327         return rc;
328 }
329
330 /* remove trailing return code */
331 gchar *strretchomp(gchar *str)
332 {
333         register gchar *s;
334
335         if (!*str) return str;
336
337         for (s = str + strlen(str) - 1;
338              s >= str && (*s == '\n' || *s == '\r');
339              s--)
340                 *s = '\0';
341
342         return str;
343 }
344
345 /* remove trailing character */
346 gchar *strtailchomp(gchar *str, gchar tail_char)
347 {
348         register gchar *s;
349
350         if (!*str) return str;
351         if (tail_char == '\0') return str;
352
353         for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
354                 *s = '\0';
355
356         return str;
357 }
358
359 /* remove CR (carriage return) */
360 gchar *strcrchomp(gchar *str)
361 {
362         register gchar *s;
363
364         if (!*str) return str;
365
366         s = str + strlen(str) - 1;
367         if (*s == '\n' && s > str && *(s - 1) == '\r') {
368                 *(s - 1) = '\n';
369                 *s = '\0';
370         }
371
372         return str;
373 }
374
375 gint file_strip_crs(const gchar *file)
376 {
377         FILE *fp = NULL, *outfp = NULL;
378         gchar buf[4096];
379         gchar *out = get_tmp_file();
380         if (file == NULL)
381                 goto freeout;
382
383         fp = g_fopen(file, "rb");
384         if (!fp)
385                 goto freeout;
386
387         outfp = g_fopen(out, "wb");
388         if (!outfp) {
389                 fclose(fp);
390                 goto freeout;
391         }
392
393         while (fgets(buf, sizeof (buf), fp) != NULL) {
394                 strcrchomp(buf);
395                 if (fputs(buf, outfp) == EOF) {
396                         fclose(fp);
397                         fclose(outfp);
398                         goto unlinkout;
399                 }
400         }
401
402         fclose(fp);
403         if (fclose(outfp) == EOF) {
404                 goto unlinkout;
405         }
406         
407         if (move_file(out, file, TRUE) < 0)
408                 goto unlinkout;
409         
410         g_free(out);
411         return 0;
412 unlinkout:
413         claws_unlink(out);
414 freeout:
415         g_free(out);
416         return -1;
417 }
418
419 /* Similar to `strstr' but this function ignores the case of both strings.  */
420 gchar *strcasestr(const gchar *haystack, const gchar *needle)
421 {
422         size_t haystack_len = strlen(haystack);
423
424         return strncasestr(haystack, haystack_len, needle);
425 }
426
427 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
428 {
429         register size_t needle_len;
430
431         needle_len   = strlen(needle);
432
433         if (haystack_len < needle_len || needle_len == 0)
434                 return NULL;
435
436         while (haystack_len >= needle_len) {
437                 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
438                         return (gchar *)haystack;
439                 else {
440                         haystack++;
441                         haystack_len--;
442                 }
443         }
444
445         return NULL;
446 }
447
448 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
449                    gconstpointer needle, size_t needlelen)
450 {
451         const gchar *haystack_ = (const gchar *)haystack;
452         const gchar *needle_ = (const gchar *)needle;
453         const gchar *haystack_cur = (const gchar *)haystack;
454         size_t haystack_left = haystacklen;
455
456         if (needlelen == 1)
457                 return memchr(haystack_, *needle_, haystacklen);
458
459         while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
460                != NULL) {
461                 if (haystacklen - (haystack_cur - haystack_) < needlelen)
462                         break;
463                 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
464                         return (gpointer)haystack_cur;
465                 else{
466                         haystack_cur++;
467                         haystack_left = haystacklen - (haystack_cur - haystack_);
468                 }
469         }
470
471         return NULL;
472 }
473
474 /* Copy no more than N characters of SRC to DEST, with NULL terminating.  */
475 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
476 {
477         register const gchar *s = src;
478         register gchar *d = dest;
479
480         while (--n && *s)
481                 *d++ = *s++;
482         *d = '\0';
483
484         return dest;
485 }
486
487
488 /* Examine if next block is non-ASCII string */
489 gboolean is_next_nonascii(const gchar *s)
490 {
491         const gchar *p;
492
493         /* skip head space */
494         for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
495                 ;
496         for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
497                 if (*(guchar *)p > 127 || *(guchar *)p < 32)
498                         return TRUE;
499         }
500
501         return FALSE;
502 }
503
504 gint get_next_word_len(const gchar *s)
505 {
506         gint len = 0;
507
508         for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
509                 ;
510
511         return len;
512 }
513
514 static void trim_subject_for_compare(gchar *str)
515 {
516         gchar *srcp;
517
518         eliminate_parenthesis(str, '[', ']');
519         eliminate_parenthesis(str, '(', ')');
520         g_strstrip(str);
521
522         srcp = str + subject_get_prefix_length(str);
523         if (srcp != str)
524                 memmove(str, srcp, strlen(srcp) + 1);
525 }
526
527 static void trim_subject_for_sort(gchar *str)
528 {
529         gchar *srcp;
530
531         g_strstrip(str);
532
533         srcp = str + subject_get_prefix_length(str);
534         if (srcp != str)
535                 memmove(str, srcp, strlen(srcp) + 1);
536 }
537
538 /* compare subjects */
539 gint subject_compare(const gchar *s1, const gchar *s2)
540 {
541         gchar *str1, *str2;
542
543         if (!s1 || !s2) return -1;
544         if (!*s1 || !*s2) return -1;
545
546         Xstrdup_a(str1, s1, return -1);
547         Xstrdup_a(str2, s2, return -1);
548
549         trim_subject_for_compare(str1);
550         trim_subject_for_compare(str2);
551
552         if (!*str1 || !*str2) return -1;
553
554         return strcmp(str1, str2);
555 }
556
557 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
558 {
559         gchar *str1, *str2;
560
561         if (!s1 || !s2) return -1;
562
563         Xstrdup_a(str1, s1, return -1);
564         Xstrdup_a(str2, s2, return -1);
565
566         trim_subject_for_sort(str1);
567         trim_subject_for_sort(str2);
568
569         return g_utf8_collate(str1, str2);
570 }
571
572 void trim_subject(gchar *str)
573 {
574         register gchar *srcp;
575         gchar op, cl;
576         gint in_brace;
577
578         g_strstrip(str);
579
580         srcp = str + subject_get_prefix_length(str);
581
582         if (*srcp == '[') {
583                 op = '[';
584                 cl = ']';
585         } else if (*srcp == '(') {
586                 op = '(';
587                 cl = ')';
588         } else
589                 op = 0;
590
591         if (op) {
592                 ++srcp;
593                 in_brace = 1;
594                 while (*srcp) {
595                         if (*srcp == op)
596                                 in_brace++;
597                         else if (*srcp == cl)
598                                 in_brace--;
599                         srcp++;
600                         if (in_brace == 0)
601                                 break;
602                 }
603         }
604         while (g_ascii_isspace(*srcp)) srcp++;
605         memmove(str, srcp, strlen(srcp) + 1);
606 }
607
608 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
609 {
610         register gchar *srcp, *destp;
611         gint in_brace;
612
613         destp = str;
614
615         while ((destp = strchr(destp, op))) {
616                 in_brace = 1;
617                 srcp = destp + 1;
618                 while (*srcp) {
619                         if (*srcp == op)
620                                 in_brace++;
621                         else if (*srcp == cl)
622                                 in_brace--;
623                         srcp++;
624                         if (in_brace == 0)
625                                 break;
626                 }
627                 while (g_ascii_isspace(*srcp)) srcp++;
628                 memmove(destp, srcp, strlen(srcp) + 1);
629         }
630 }
631
632 void extract_parenthesis(gchar *str, gchar op, gchar cl)
633 {
634         register gchar *srcp, *destp;
635         gint in_brace;
636
637         destp = str;
638
639         while ((srcp = strchr(destp, op))) {
640                 if (destp > str)
641                         *destp++ = ' ';
642                 memmove(destp, srcp + 1, strlen(srcp));
643                 in_brace = 1;
644                 while(*destp) {
645                         if (*destp == op)
646                                 in_brace++;
647                         else if (*destp == cl)
648                                 in_brace--;
649
650                         if (in_brace == 0)
651                                 break;
652
653                         destp++;
654                 }
655         }
656         *destp = '\0';
657 }
658
659 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
660                                          gchar op, gchar cl)
661 {
662         register gchar *srcp, *destp;
663         gint in_brace;
664         gboolean in_quote = FALSE;
665
666         destp = str;
667
668         while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
669                 if (destp > str)
670                         *destp++ = ' ';
671                 memmove(destp, srcp + 1, strlen(srcp));
672                 in_brace = 1;
673                 while(*destp) {
674                         if (*destp == op && !in_quote)
675                                 in_brace++;
676                         else if (*destp == cl && !in_quote)
677                                 in_brace--;
678                         else if (*destp == quote_chr)
679                                 in_quote ^= TRUE;
680
681                         if (in_brace == 0)
682                                 break;
683
684                         destp++;
685                 }
686         }
687         *destp = '\0';
688 }
689
690 void extract_quote(gchar *str, gchar quote_chr)
691 {
692         register gchar *p;
693
694         if ((str = strchr(str, quote_chr))) {
695                 p = str;
696                 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
697                         memmove(p - 1, p, strlen(p) + 1);
698                         p--;
699                 }
700                 if(p) {
701                         *p = '\0';
702                         memmove(str, str + 1, p - str);
703                 }
704         }
705 }
706
707 /* Returns a newly allocated string with all quote_chr not at the beginning
708    or the end of str escaped with '\' or the given str if not required. */
709 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
710 {
711         register gchar *p, *q;
712         gchar *qstr;
713         int k = 0, l = 0;
714
715         if (str == NULL || *str == '\0')
716                 return str;
717
718         /* search for unescaped quote_chr */
719         p = str;
720         if (*p == quote_chr)
721                 ++p, ++l;
722         while (*p) {
723                 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
724                         ++k;
725                 ++p, ++l;
726         }
727         if (!k) /* nothing to escape */
728                 return str;
729
730         /* unescaped quote_chr found */
731         qstr = g_malloc(l + k + 1);
732         p = str;
733         q = qstr;
734         if (*p == quote_chr) {
735                 *q = quote_chr;
736                 ++p, ++q;
737         }
738         while (*p) {
739                 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
740                         *q++ = '\\';
741                 *q++ = *p++;
742         }
743         *q = '\0';
744
745         return qstr;
746 }
747
748 void eliminate_address_comment(gchar *str)
749 {
750         register gchar *srcp, *destp;
751         gint in_brace;
752
753         destp = str;
754
755         while ((destp = strchr(destp, '"'))) {
756                 if ((srcp = strchr(destp + 1, '"'))) {
757                         srcp++;
758                         if (*srcp == '@') {
759                                 destp = srcp + 1;
760                         } else {
761                                 while (g_ascii_isspace(*srcp)) srcp++;
762                                 memmove(destp, srcp, strlen(srcp) + 1);
763                         }
764                 } else {
765                         *destp = '\0';
766                         break;
767                 }
768         }
769
770         destp = str;
771
772         while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
773                 in_brace = 1;
774                 srcp = destp + 1;
775                 while (*srcp) {
776                         if (*srcp == '(')
777                                 in_brace++;
778                         else if (*srcp == ')')
779                                 in_brace--;
780                         srcp++;
781                         if (in_brace == 0)
782                                 break;
783                 }
784                 while (g_ascii_isspace(*srcp)) srcp++;
785                 memmove(destp, srcp, strlen(srcp) + 1);
786         }
787 }
788
789 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
790 {
791         gboolean in_quote = FALSE;
792
793         while (*str) {
794                 if (*str == c && !in_quote)
795                         return (gchar *)str;
796                 if (*str == quote_chr)
797                         in_quote ^= TRUE;
798                 str++;
799         }
800
801         return NULL;
802 }
803
804 void extract_address(gchar *str)
805 {
806         cm_return_if_fail(str != NULL);
807         eliminate_address_comment(str);
808         if (strchr_with_skip_quote(str, '"', '<'))
809                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
810         g_strstrip(str);
811 }
812
813 void extract_list_id_str(gchar *str)
814 {
815         if (strchr_with_skip_quote(str, '"', '<'))
816                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
817         g_strstrip(str);
818 }
819
820 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
821 {
822         gchar *work;
823         gchar *workp;
824
825         if (!str) return addr_list;
826
827         Xstrdup_a(work, str, return addr_list);
828
829         if (removecomments)
830                 eliminate_address_comment(work);
831         workp = work;
832
833         while (workp && *workp) {
834                 gchar *p, *next;
835
836                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
837                         *p = '\0';
838                         next = p + 1;
839                 } else
840                         next = NULL;
841
842                 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
843                         extract_parenthesis_with_skip_quote
844                                 (workp, '"', '<', '>');
845
846                 g_strstrip(workp);
847                 if (*workp)
848                         addr_list = g_slist_append(addr_list, g_strdup(workp));
849
850                 workp = next;
851         }
852
853         return addr_list;
854 }
855
856 GSList *address_list_append(GSList *addr_list, const gchar *str)
857 {
858         return address_list_append_real(addr_list, str, TRUE);
859 }
860
861 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
862 {
863         return address_list_append_real(addr_list, str, FALSE);
864 }
865
866 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
867 {
868         const gchar *strp;
869
870         if (!str) return msgid_list;
871         strp = str;
872
873         while (strp && *strp) {
874                 const gchar *start, *end;
875                 gchar *msgid;
876
877                 if ((start = strchr(strp, '<')) != NULL) {
878                         end = strchr(start + 1, '>');
879                         if (!end) break;
880                 } else
881                         break;
882
883                 msgid = g_strndup(start + 1, end - start - 1);
884                 g_strstrip(msgid);
885                 if (*msgid)
886                         msgid_list = g_slist_prepend(msgid_list, msgid);
887                 else
888                         g_free(msgid);
889
890                 strp = end + 1;
891         }
892
893         return msgid_list;
894 }
895
896 GSList *references_list_append(GSList *msgid_list, const gchar *str)
897 {
898         GSList *list;
899
900         list = references_list_prepend(NULL, str);
901         list = g_slist_reverse(list);
902         msgid_list = g_slist_concat(msgid_list, list);
903
904         return msgid_list;
905 }
906
907 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
908 {
909         gchar *work;
910         gchar *workp;
911
912         if (!str) return group_list;
913
914         Xstrdup_a(work, str, return group_list);
915
916         workp = work;
917
918         while (workp && *workp) {
919                 gchar *p, *next;
920
921                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
922                         *p = '\0';
923                         next = p + 1;
924                 } else
925                         next = NULL;
926
927                 g_strstrip(workp);
928                 if (*workp)
929                         group_list = g_slist_append(group_list,
930                                                     g_strdup(workp));
931
932                 workp = next;
933         }
934
935         return group_list;
936 }
937
938 GList *add_history(GList *list, const gchar *str)
939 {
940         GList *old;
941         gchar *oldstr;
942
943         cm_return_val_if_fail(str != NULL, list);
944
945         old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
946         if (old) {
947                 oldstr = old->data;
948                 list = g_list_remove(list, old->data);
949                 g_free(oldstr);
950         } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
951                 GList *last;
952
953                 last = g_list_last(list);
954                 if (last) {
955                         oldstr = last->data;
956                         list = g_list_remove(list, last->data);
957                         g_free(oldstr);
958                 }
959         }
960
961         list = g_list_prepend(list, g_strdup(str));
962
963         return list;
964 }
965
966 void remove_return(gchar *str)
967 {
968         register gchar *p = str;
969
970         while (*p) {
971                 if (*p == '\n' || *p == '\r')
972                         memmove(p, p + 1, strlen(p));
973                 else
974                         p++;
975         }
976 }
977
978 void remove_space(gchar *str)
979 {
980         register gchar *p = str;
981         register gint spc;
982
983         while (*p) {
984                 spc = 0;
985                 while (g_ascii_isspace(*(p + spc)))
986                         spc++;
987                 if (spc)
988                         memmove(p, p + spc, strlen(p + spc) + 1);
989                 else
990                         p++;
991         }
992 }
993
994 void unfold_line(gchar *str)
995 {
996         register gchar *p = str;
997         register gint spc;
998
999         while (*p) {
1000                 if (*p == '\n' || *p == '\r') {
1001                         *p++ = ' ';
1002                         spc = 0;
1003                         while (g_ascii_isspace(*(p + spc)))
1004                                 spc++;
1005                         if (spc)
1006                                 memmove(p, p + spc, strlen(p + spc) + 1);
1007                 } else
1008                         p++;
1009         }
1010 }
1011
1012 void subst_char(gchar *str, gchar orig, gchar subst)
1013 {
1014         register gchar *p = str;
1015
1016         while (*p) {
1017                 if (*p == orig)
1018                         *p = subst;
1019                 p++;
1020         }
1021 }
1022
1023 void subst_chars(gchar *str, gchar *orig, gchar subst)
1024 {
1025         register gchar *p = str;
1026
1027         while (*p) {
1028                 if (strchr(orig, *p) != NULL)
1029                         *p = subst;
1030                 p++;
1031         }
1032 }
1033
1034 void subst_for_filename(gchar *str)
1035 {
1036         if (!str)
1037                 return;
1038 #ifdef G_OS_WIN32
1039         subst_chars(str, "\t\r\n\\/*:", '_');
1040 #else
1041         subst_chars(str, "\t\r\n\\/*", '_');
1042 #endif
1043 }
1044
1045 void subst_for_shellsafe_filename(gchar *str)
1046 {
1047         if (!str)
1048                 return;
1049         subst_for_filename(str);
1050         subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1051 }
1052
1053 gboolean is_ascii_str(const gchar *str)
1054 {
1055         const guchar *p = (const guchar *)str;
1056
1057         while (*p != '\0') {
1058                 if (*p != '\t' && *p != ' ' &&
1059                     *p != '\r' && *p != '\n' &&
1060                     (*p < 32 || *p >= 127))
1061                         return FALSE;
1062                 p++;
1063         }
1064
1065         return TRUE;
1066 }
1067
1068 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1069 {
1070         gchar * position = NULL;
1071         gchar * tmp_pos = NULL;
1072         int i;
1073
1074         if (quote_chars == NULL)
1075                 return NULL;
1076
1077         for (i = 0; i < strlen(quote_chars); i++) {
1078                 tmp_pos = strrchr (str, quote_chars[i]);
1079                 if(position == NULL
1080                    || (tmp_pos != NULL && position <= tmp_pos) )
1081                         position = tmp_pos;
1082         }
1083         return position;
1084 }
1085
1086 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1087 {
1088         const gchar *first_pos;
1089         const gchar *last_pos;
1090         const gchar *p = str;
1091         gint quote_level = -1;
1092
1093         /* speed up line processing by only searching to the last '>' */
1094         if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1095                 /* skip a line if it contains a '<' before the initial '>' */
1096                 if (memchr(str, '<', first_pos - str) != NULL)
1097                         return -1;
1098                 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1099         } else
1100                 return -1;
1101
1102         while (p <= last_pos) {
1103                 while (p < last_pos) {
1104                         if (g_ascii_isspace(*p))
1105                                 p++;
1106                         else
1107                                 break;
1108                 }
1109
1110                 if (strchr(quote_chars, *p))
1111                         quote_level++;
1112                 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1113                         /* any characters are allowed except '-','<' and space */
1114                         while (*p != '-' && *p != '<'
1115                                && !strchr(quote_chars, *p)
1116                                && !g_ascii_isspace(*p)
1117                                && p < last_pos)
1118                                 p++;
1119                         if (strchr(quote_chars, *p))
1120                                 quote_level++;
1121                         else
1122                                 break;
1123                 }
1124
1125                 p++;
1126         }
1127
1128         return quote_level;
1129 }
1130
1131 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1132 {
1133         const gchar *p = str, *q;
1134         gint cur_line = 0, len;
1135
1136         while ((q = strchr(p, '\n')) != NULL) {
1137                 len = q - p + 1;
1138                 if (len > max_chars) {
1139                         if (line)
1140                                 *line = cur_line;
1141                         return -1;
1142                 }
1143                 p = q + 1;
1144                 ++cur_line;
1145         }
1146
1147         len = strlen(p);
1148         if (len > max_chars) {
1149                 if (line)
1150                         *line = cur_line;
1151                 return -1;
1152         }
1153
1154         return 0;
1155 }
1156
1157 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1158 {
1159         gchar * position = NULL;
1160         gchar * tmp_pos = NULL;
1161         int i;
1162
1163         if (quote_chars == NULL)
1164                 return FALSE;
1165
1166         for (i = 0; i < strlen(quote_chars); i++) {
1167                 tmp_pos = strchr (str,  quote_chars[i]);
1168                 if(position == NULL
1169                    || (tmp_pos != NULL && position >= tmp_pos) )
1170                         position = tmp_pos;
1171         }
1172         return position;
1173 }
1174
1175 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1176 {
1177         register guint haystack_len, needle_len;
1178         gboolean in_squote = FALSE, in_dquote = FALSE;
1179
1180         haystack_len = strlen(haystack);
1181         needle_len   = strlen(needle);
1182
1183         if (haystack_len < needle_len || needle_len == 0)
1184                 return NULL;
1185
1186         while (haystack_len >= needle_len) {
1187                 if (!in_squote && !in_dquote &&
1188                     !strncmp(haystack, needle, needle_len))
1189                         return (gchar *)haystack;
1190
1191                 /* 'foo"bar"' -> foo"bar"
1192                    "foo'bar'" -> foo'bar' */
1193                 if (*haystack == '\'') {
1194                         if (in_squote)
1195                                 in_squote = FALSE;
1196                         else if (!in_dquote)
1197                                 in_squote = TRUE;
1198                 } else if (*haystack == '\"') {
1199                         if (in_dquote)
1200                                 in_dquote = FALSE;
1201                         else if (!in_squote)
1202                                 in_dquote = TRUE;
1203                 } else if (*haystack == '\\') {
1204                         haystack++;
1205                         haystack_len--;
1206                 }
1207
1208                 haystack++;
1209                 haystack_len--;
1210         }
1211
1212         return NULL;
1213 }
1214
1215 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1216                             gint max_tokens)
1217 {
1218         GSList *string_list = NULL, *slist;
1219         gchar **str_array, *s, *new_str;
1220         guint i, n = 1, len;
1221
1222         cm_return_val_if_fail(str != NULL, NULL);
1223         cm_return_val_if_fail(delim != NULL, NULL);
1224
1225         if (max_tokens < 1)
1226                 max_tokens = G_MAXINT;
1227
1228         s = strstr_with_skip_quote(str, delim);
1229         if (s) {
1230                 guint delimiter_len = strlen(delim);
1231
1232                 do {
1233                         len = s - str;
1234                         new_str = g_strndup(str, len);
1235
1236                         if (new_str[0] == '\'' || new_str[0] == '\"') {
1237                                 if (new_str[len - 1] == new_str[0]) {
1238                                         new_str[len - 1] = '\0';
1239                                         memmove(new_str, new_str + 1, len - 1);
1240                                 }
1241                         }
1242                         string_list = g_slist_prepend(string_list, new_str);
1243                         n++;
1244                         str = s + delimiter_len;
1245                         s = strstr_with_skip_quote(str, delim);
1246                 } while (--max_tokens && s);
1247         }
1248
1249         if (*str) {
1250                 new_str = g_strdup(str);
1251                 if (new_str[0] == '\'' || new_str[0] == '\"') {
1252                         len = strlen(str);
1253                         if (new_str[len - 1] == new_str[0]) {
1254                                 new_str[len - 1] = '\0';
1255                                 memmove(new_str, new_str + 1, len - 1);
1256                         }
1257                 }
1258                 string_list = g_slist_prepend(string_list, new_str);
1259                 n++;
1260         }
1261
1262         str_array = g_new(gchar*, n);
1263
1264         i = n - 1;
1265
1266         str_array[i--] = NULL;
1267         for (slist = string_list; slist; slist = slist->next)
1268                 str_array[i--] = slist->data;
1269
1270         g_slist_free(string_list);
1271
1272         return str_array;
1273 }
1274
1275 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1276 {
1277         gchar *abbrev_group;
1278         gchar *ap;
1279         const gchar *p = group;
1280         const gchar *last;
1281
1282         cm_return_val_if_fail(group != NULL, NULL);
1283
1284         last = group + strlen(group);
1285         abbrev_group = ap = g_malloc(strlen(group) + 1);
1286
1287         while (*p) {
1288                 while (*p == '.')
1289                         *ap++ = *p++;
1290                 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1291                         *ap++ = *p++;
1292                         while (*p != '.') p++;
1293                 } else {
1294                         strcpy(ap, p);
1295                         return abbrev_group;
1296                 }
1297         }
1298
1299         *ap = '\0';
1300         return abbrev_group;
1301 }
1302
1303 gchar *trim_string(const gchar *str, gint len)
1304 {
1305         const gchar *p = str;
1306         gint mb_len;
1307         gchar *new_str;
1308         gint new_len = 0;
1309
1310         if (!str) return NULL;
1311         if (strlen(str) <= len)
1312                 return g_strdup(str);
1313         if (g_utf8_validate(str, -1, NULL) == FALSE)
1314                 return g_strdup(str);
1315
1316         while (*p != '\0') {
1317                 mb_len = g_utf8_skip[*(guchar *)p];
1318                 if (mb_len == 0)
1319                         break;
1320                 else if (new_len + mb_len > len)
1321                         break;
1322
1323                 new_len += mb_len;
1324                 p += mb_len;
1325         }
1326
1327         Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1328         return g_strconcat(new_str, "...", NULL);
1329 }
1330
1331 GList *uri_list_extract_filenames(const gchar *uri_list)
1332 {
1333         GList *result = NULL;
1334         const gchar *p, *q;
1335         gchar *escaped_utf8uri;
1336
1337         p = uri_list;
1338
1339         while (p) {
1340                 if (*p != '#') {
1341                         while (g_ascii_isspace(*p)) p++;
1342                         if (!strncmp(p, "file:", 5)) {
1343                                 q = p;
1344                                 q += 5;
1345                                 while (*q && *q != '\n' && *q != '\r') q++;
1346
1347                                 if (q > p) {
1348                                         gchar *file, *locale_file = NULL;
1349                                         q--;
1350                                         while (q > p && g_ascii_isspace(*q))
1351                                                 q--;
1352                                         Xalloca(escaped_utf8uri, q - p + 2,
1353                                                 return result);
1354                                         Xalloca(file, q - p + 2,
1355                                                 return result);
1356                                         *file = '\0';
1357                                         strncpy(escaped_utf8uri, p, q - p + 1);
1358                                         escaped_utf8uri[q - p + 1] = '\0';
1359                                         decode_uri(file, escaped_utf8uri);
1360                     /*
1361                      * g_filename_from_uri() rejects escaped/locale encoded uri
1362                      * string which come from Nautilus.
1363                      */
1364 #ifndef G_OS_WIN32
1365                                         if (g_utf8_validate(file, -1, NULL))
1366                                                 locale_file
1367                                                         = conv_codeset_strdup(
1368                                                                 file + 5,
1369                                                                 CS_UTF_8,
1370                                                                 conv_get_locale_charset_str());
1371                                         if (!locale_file)
1372                                                 locale_file = g_strdup(file + 5);
1373 #else
1374                                         locale_file = g_filename_from_uri(escaped_utf8uri, NULL, NULL);
1375 #endif
1376                                         result = g_list_append(result, locale_file);
1377                                 }
1378                         }
1379                 }
1380                 p = strchr(p, '\n');
1381                 if (p) p++;
1382         }
1383
1384         return result;
1385 }
1386
1387 /* Converts two-digit hexadecimal to decimal.  Used for unescaping escaped
1388  * characters
1389  */
1390 static gint axtoi(const gchar *hexstr)
1391 {
1392         gint hi, lo, result;
1393
1394         hi = hexstr[0];
1395         if ('0' <= hi && hi <= '9') {
1396                 hi -= '0';
1397         } else
1398                 if ('a' <= hi && hi <= 'f') {
1399                         hi -= ('a' - 10);
1400                 } else
1401                         if ('A' <= hi && hi <= 'F') {
1402                                 hi -= ('A' - 10);
1403                         }
1404
1405         lo = hexstr[1];
1406         if ('0' <= lo && lo <= '9') {
1407                 lo -= '0';
1408         } else
1409                 if ('a' <= lo && lo <= 'f') {
1410                         lo -= ('a'-10);
1411                 } else
1412                         if ('A' <= lo && lo <= 'F') {
1413                                 lo -= ('A' - 10);
1414                         }
1415         result = lo + (16 * hi);
1416         return result;
1417 }
1418
1419 gboolean is_uri_string(const gchar *str)
1420 {
1421         while (str && *str && g_ascii_isspace(*str))
1422                 str++;
1423         return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1424                 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1425                 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1426                 g_ascii_strncasecmp(str, "www.", 4) == 0);
1427 }
1428
1429 gchar *get_uri_path(const gchar *uri)
1430 {
1431         while (uri && *uri && g_ascii_isspace(*uri))
1432                 uri++;
1433         if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1434                 return (gchar *)(uri + 7);
1435         else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1436                 return (gchar *)(uri + 8);
1437         else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1438                 return (gchar *)(uri + 6);
1439         else
1440                 return (gchar *)uri;
1441 }
1442
1443 gint get_uri_len(const gchar *str)
1444 {
1445         const gchar *p;
1446
1447         if (is_uri_string(str)) {
1448                 for (p = str; *p != '\0'; p++) {
1449                         if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1450                                 break;
1451                 }
1452                 return p - str;
1453         }
1454
1455         return 0;
1456 }
1457
1458 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1459  * plusses, and escape characters are used)
1460  */
1461 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1462 {
1463         gchar *dec = decoded_uri;
1464         const gchar *enc = encoded_uri;
1465
1466         while (*enc) {
1467                 if (*enc == '%') {
1468                         enc++;
1469                         if (isxdigit((guchar)enc[0]) &&
1470                             isxdigit((guchar)enc[1])) {
1471                                 *dec = axtoi(enc);
1472                                 dec++;
1473                                 enc += 2;
1474                         }
1475                 } else {
1476                         if (with_plus && *enc == '+')
1477                                 *dec = ' ';
1478                         else
1479                                 *dec = *enc;
1480                         dec++;
1481                         enc++;
1482                 }
1483         }
1484
1485         *dec = '\0';
1486 }
1487
1488 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1489 {
1490         decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1491 }
1492
1493 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1494 {
1495     gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1496     decode_uri_with_plus(buffer, encoded_uri, FALSE);
1497     return buffer;
1498 }
1499
1500 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1501                      gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1502 {
1503         gchar *tmp_mailto;
1504         gchar *p;
1505         const gchar *forbidden_uris[] = { ".gnupg/",
1506                                           "/etc/passwd",
1507                                           "/etc/shadow",
1508                                           ".ssh/",
1509                                           "../",
1510                                           NULL };
1511         gint num_attach = 0;
1512         gchar **my_att = NULL;
1513
1514         Xstrdup_a(tmp_mailto, mailto, return -1);
1515
1516         if (!strncmp(tmp_mailto, "mailto:", 7))
1517                 tmp_mailto += 7;
1518
1519         p = strchr(tmp_mailto, '?');
1520         if (p) {
1521                 *p = '\0';
1522                 p++;
1523         }
1524
1525         if (to && !*to)
1526                 *to = decode_uri_gdup(tmp_mailto);
1527
1528         my_att = g_malloc(sizeof(char *));
1529         my_att[0] = NULL;
1530
1531         while (p) {
1532                 gchar *field, *value;
1533
1534                 field = p;
1535
1536                 p = strchr(p, '=');
1537                 if (!p) break;
1538                 *p = '\0';
1539                 p++;
1540
1541                 value = p;
1542
1543                 p = strchr(p, '&');
1544                 if (p) {
1545                         *p = '\0';
1546                         p++;
1547                 }
1548
1549                 if (*value == '\0') continue;
1550
1551                 if (from && !g_ascii_strcasecmp(field, "from")) {
1552                         if (!*from) {
1553                                 *from = decode_uri_gdup(value);
1554                         } else {
1555                                 gchar *tmp = decode_uri_gdup(value);
1556                                 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1557                                 g_free(*from);
1558                                 *from = new_from;
1559                         }
1560                 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1561                         if (!*cc) {
1562                                 *cc = decode_uri_gdup(value);
1563                         } else {
1564                                 gchar *tmp = decode_uri_gdup(value);
1565                                 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1566                                 g_free(*cc);
1567                                 *cc = new_cc;
1568                         }
1569                 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1570                         if (!*bcc) {
1571                                 *bcc = decode_uri_gdup(value);
1572                         } else {
1573                                 gchar *tmp = decode_uri_gdup(value);
1574                                 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1575                                 g_free(*bcc);
1576                                 *bcc = new_bcc;
1577                         }
1578                 } else if (subject && !*subject &&
1579                            !g_ascii_strcasecmp(field, "subject")) {
1580                         *subject = decode_uri_gdup(value);
1581                 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1582                         *body = decode_uri_gdup(value);
1583                 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1584                         gchar *tmp = decode_uri_gdup(value);
1585                         if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1586                                 g_warning("couldn't set insert file '%s' in body", value);
1587                         }
1588                         g_free(tmp);
1589                         tmp = NULL;
1590                 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1591                         int i = 0;
1592                         gchar *tmp = decode_uri_gdup(value);
1593                         for (; forbidden_uris[i]; i++) {
1594                                 if (strstr(tmp, forbidden_uris[i])) {
1595                                         g_print("Refusing to attach '%s', potential private data leak\n",
1596                                                         tmp);
1597                                         g_free(tmp);
1598                                         tmp = NULL;
1599                                         break;
1600                                 }
1601                         }
1602                         if (tmp) {
1603                                 /* attach is correct */
1604                                 num_attach++;
1605                                 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1606                                 my_att[num_attach-1] = tmp;
1607                                 my_att[num_attach] = NULL;
1608                         }
1609                 } else if (inreplyto && !*inreplyto &&
1610                            !g_ascii_strcasecmp(field, "in-reply-to")) {
1611                         *inreplyto = decode_uri_gdup(value);
1612                 }
1613         }
1614
1615         if (attach)
1616                 *attach = my_att;
1617         return 0;
1618 }
1619
1620
1621 #ifdef G_OS_WIN32
1622 #include <windows.h>
1623 #ifndef CSIDL_APPDATA
1624 #define CSIDL_APPDATA 0x001a
1625 #endif
1626 #ifndef CSIDL_LOCAL_APPDATA
1627 #define CSIDL_LOCAL_APPDATA 0x001c
1628 #endif
1629 #ifndef CSIDL_FLAG_CREATE
1630 #define CSIDL_FLAG_CREATE 0x8000
1631 #endif
1632 #define DIM(v)               (sizeof(v)/sizeof((v)[0]))
1633
1634 #define RTLD_LAZY 0
1635 const char *
1636 w32_strerror (int w32_errno)
1637 {
1638   static char strerr[256];
1639   int ec = (int)GetLastError ();
1640
1641   if (w32_errno == 0)
1642     w32_errno = ec;
1643   FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1644                  MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1645                  strerr, DIM (strerr)-1, NULL);
1646   return strerr;
1647 }
1648
1649 static __inline__ void *
1650 dlopen (const char * name, int flag)
1651 {
1652   void * hd = LoadLibrary (name);
1653   return hd;
1654 }
1655
1656 static __inline__ void *
1657 dlsym (void * hd, const char * sym)
1658 {
1659   if (hd && sym)
1660     {
1661       void * fnc = GetProcAddress (hd, sym);
1662       if (!fnc)
1663         return NULL;
1664       return fnc;
1665     }
1666   return NULL;
1667 }
1668
1669
1670 static __inline__ const char *
1671 dlerror (void)
1672 {
1673   return w32_strerror (0);
1674 }
1675
1676
1677 static __inline__ int
1678 dlclose (void * hd)
1679 {
1680   if (hd)
1681     {
1682       FreeLibrary (hd);
1683       return 0;
1684     }
1685   return -1;
1686 }
1687
1688 static HRESULT
1689 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1690 {
1691   static int initialized;
1692   static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1693
1694   if (!initialized)
1695     {
1696       static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1697       void *handle;
1698       int i;
1699
1700       initialized = 1;
1701
1702       for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1703         {
1704           handle = dlopen (dllnames[i], RTLD_LAZY);
1705           if (handle)
1706             {
1707               func = dlsym (handle, "SHGetFolderPathW");
1708               if (!func)
1709                 {
1710                   dlclose (handle);
1711                   handle = NULL;
1712                 }
1713             }
1714         }
1715     }
1716
1717   if (func)
1718     return func (a,b,c,d,e);
1719   else
1720     return -1;
1721 }
1722
1723 /* Returns a static string with the directroy from which the module
1724    has been loaded.  Returns an empty string on error. */
1725 static char *w32_get_module_dir(void)
1726 {
1727         static char *moddir;
1728
1729         if (!moddir) {
1730                 char name[MAX_PATH+10];
1731                 char *p;
1732
1733                 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1734                         *name = 0;
1735                 else {
1736                         p = strrchr (name, '\\');
1737                         if (p)
1738                                 *p = 0;
1739                         else
1740                                 *name = 0;
1741                 }
1742                 moddir = g_strdup (name);
1743         }
1744         return moddir;
1745 }
1746 #endif /* G_OS_WIN32 */
1747
1748 /* Return a static string with the locale dir. */
1749 const gchar *get_locale_dir(void)
1750 {
1751         static gchar *loc_dir;
1752
1753 #ifdef G_OS_WIN32
1754         if (!loc_dir)
1755                 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1756                                       "\\share\\locale", NULL);
1757 #endif
1758         if (!loc_dir)
1759                 loc_dir = LOCALEDIR;
1760         
1761         return loc_dir;
1762 }
1763
1764
1765 const gchar *get_home_dir(void)
1766 {
1767 #ifdef G_OS_WIN32
1768         static char home_dir_utf16[MAX_PATH] = "";
1769         static gchar *home_dir_utf8 = NULL;
1770         if (home_dir_utf16[0] == '\0') {
1771                 if (w32_shgetfolderpath
1772                             (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1773                              NULL, 0, home_dir_utf16) < 0)
1774                                 strcpy (home_dir_utf16, "C:\\Sylpheed");
1775                 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1776         }
1777         return home_dir_utf8;
1778 #else
1779         static const gchar *homeenv = NULL;
1780
1781         if (homeenv)
1782                 return homeenv;
1783
1784         if (!homeenv && g_getenv("HOME") != NULL)
1785                 homeenv = g_strdup(g_getenv("HOME"));
1786         if (!homeenv)
1787                 homeenv = g_get_home_dir();
1788
1789         return homeenv;
1790 #endif
1791 }
1792
1793 static gchar *claws_rc_dir = NULL;
1794 static gboolean rc_dir_alt = FALSE;
1795 const gchar *get_rc_dir(void)
1796 {
1797
1798         if (!claws_rc_dir) {
1799                 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1800                                      RC_DIR, NULL);
1801                 debug_print("using default rc_dir %s\n", claws_rc_dir);
1802         }
1803         return claws_rc_dir;
1804 }
1805
1806 void set_rc_dir(const gchar *dir)
1807 {
1808         gchar *canonical_dir;
1809         if (claws_rc_dir != NULL) {
1810                 g_print("Error: rc_dir already set\n");
1811         } else {
1812                 int err = cm_canonicalize_filename(dir, &canonical_dir);
1813                 int len;
1814
1815                 if (err) {
1816                         g_print("Error looking for %s: %d(%s)\n",
1817                                 dir, -err, g_strerror(-err));
1818                         exit(0);
1819                 }
1820                 rc_dir_alt = TRUE;
1821
1822                 claws_rc_dir = canonical_dir;
1823                 
1824                 len = strlen(claws_rc_dir);
1825                 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1826                         claws_rc_dir[len - 1] = '\0';
1827                 
1828                 debug_print("set rc_dir to %s\n", claws_rc_dir);
1829                 if (!is_dir_exist(claws_rc_dir)) {
1830                         if (make_dir_hier(claws_rc_dir) != 0) {
1831                                 g_print("Error: can't create %s\n",
1832                                 claws_rc_dir);
1833                                 exit(0);
1834                         }
1835                 }
1836         }
1837 }
1838
1839 gboolean rc_dir_is_alt(void) {
1840         return rc_dir_alt;
1841 }
1842
1843 const gchar *get_mail_base_dir(void)
1844 {
1845         return get_home_dir();
1846 }
1847
1848 const gchar *get_news_cache_dir(void)
1849 {
1850         static gchar *news_cache_dir = NULL;
1851         if (!news_cache_dir)
1852                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1853                                              NEWS_CACHE_DIR, NULL);
1854
1855         return news_cache_dir;
1856 }
1857
1858 const gchar *get_imap_cache_dir(void)
1859 {
1860         static gchar *imap_cache_dir = NULL;
1861
1862         if (!imap_cache_dir)
1863                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1864                                              IMAP_CACHE_DIR, NULL);
1865
1866         return imap_cache_dir;
1867 }
1868
1869 const gchar *get_mime_tmp_dir(void)
1870 {
1871         static gchar *mime_tmp_dir = NULL;
1872
1873         if (!mime_tmp_dir)
1874                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1875                                            MIME_TMP_DIR, NULL);
1876
1877         return mime_tmp_dir;
1878 }
1879
1880 const gchar *get_template_dir(void)
1881 {
1882         static gchar *template_dir = NULL;
1883
1884         if (!template_dir)
1885                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1886                                            TEMPLATE_DIR, NULL);
1887
1888         return template_dir;
1889 }
1890
1891 #ifdef G_OS_WIN32
1892 const gchar *get_cert_file(void)
1893 {
1894         const gchar *cert_file = NULL;
1895         if (!cert_file)
1896                 cert_file = g_strconcat(w32_get_module_dir(),
1897                                  "\\share\\claws-mail\\",
1898                                 "ca-certificates.crt",
1899                                 NULL);  
1900         return cert_file;
1901 }
1902 #endif
1903
1904 /* Return the filepath of the claws-mail.desktop file */
1905 const gchar *get_desktop_file(void)
1906 {
1907 #ifdef DESKTOPFILEPATH
1908   return DESKTOPFILEPATH;
1909 #else
1910   return NULL;
1911 #endif
1912 }
1913
1914 /* Return the default directory for Plugins. */
1915 const gchar *get_plugin_dir(void)
1916 {
1917 #ifdef G_OS_WIN32
1918         static gchar *plugin_dir = NULL;
1919
1920         if (!plugin_dir)
1921                 plugin_dir = g_strconcat(w32_get_module_dir(),
1922                                          "\\lib\\claws-mail\\plugins\\",
1923                                          NULL);
1924         return plugin_dir;
1925 #else
1926         if (is_dir_exist(PLUGINDIR))
1927                 return PLUGINDIR;
1928         else {
1929                 static gchar *plugin_dir = NULL;
1930                 if (!plugin_dir)
1931                         plugin_dir = g_strconcat(get_rc_dir(), 
1932                                 G_DIR_SEPARATOR_S, "plugins", 
1933                                 G_DIR_SEPARATOR_S, NULL);
1934                 return plugin_dir;                      
1935         }
1936 #endif
1937 }
1938
1939
1940 #ifdef G_OS_WIN32
1941 /* Return the default directory for Themes. */
1942 const gchar *get_themes_dir(void)
1943 {
1944         static gchar *themes_dir = NULL;
1945
1946         if (!themes_dir)
1947                 themes_dir = g_strconcat(w32_get_module_dir(),
1948                                          "\\share\\claws-mail\\themes",
1949                                          NULL);
1950         return themes_dir;
1951 }
1952 #endif
1953
1954 const gchar *get_tmp_dir(void)
1955 {
1956         static gchar *tmp_dir = NULL;
1957
1958         if (!tmp_dir)
1959                 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1960                                       TMP_DIR, NULL);
1961
1962         return tmp_dir;
1963 }
1964
1965 gchar *get_tmp_file(void)
1966 {
1967         gchar *tmp_file;
1968         static guint32 id = 0;
1969
1970         tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1971                                    get_tmp_dir(), G_DIR_SEPARATOR, id++);
1972
1973         return tmp_file;
1974 }
1975
1976 const gchar *get_domain_name(void)
1977 {
1978 #ifdef G_OS_UNIX
1979         static gchar *domain_name = NULL;
1980         struct addrinfo hints, *res;
1981         char hostname[256];
1982         int s;
1983
1984         if (!domain_name) {
1985                 if (gethostname(hostname, sizeof(hostname)) != 0) {
1986                         perror("gethostname");
1987                         domain_name = "localhost";
1988                 } else {
1989                         memset(&hints, 0, sizeof(struct addrinfo));
1990                         hints.ai_family = AF_UNSPEC;
1991                         hints.ai_socktype = 0;
1992                         hints.ai_flags = AI_CANONNAME;
1993                         hints.ai_protocol = 0;
1994
1995                         s = getaddrinfo(hostname, NULL, &hints, &res);
1996                         if (s != 0) {
1997                                 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
1998                                 domain_name = g_strdup(hostname);
1999                         } else {
2000                                 domain_name = g_strdup(res->ai_canonname);
2001                                 freeaddrinfo(res);
2002                         }
2003                 }
2004                 debug_print("domain name = %s\n", domain_name);
2005         }
2006
2007         return domain_name;
2008 #else
2009         return "localhost";
2010 #endif
2011 }
2012
2013 off_t get_file_size(const gchar *file)
2014 {
2015         GStatBuf s;
2016
2017         if (g_stat(file, &s) < 0) {
2018                 FILE_OP_ERROR(file, "stat");
2019                 return -1;
2020         }
2021
2022         return s.st_size;
2023 }
2024
2025 time_t get_file_mtime(const gchar *file)
2026 {
2027         GStatBuf s;
2028
2029         if (g_stat(file, &s) < 0) {
2030                 FILE_OP_ERROR(file, "stat");
2031                 return -1;
2032         }
2033
2034         return s.st_mtime;
2035 }
2036
2037 off_t get_file_size_as_crlf(const gchar *file)
2038 {
2039         FILE *fp;
2040         off_t size = 0;
2041         gchar buf[BUFFSIZE];
2042
2043         if ((fp = g_fopen(file, "rb")) == NULL) {
2044                 FILE_OP_ERROR(file, "g_fopen");
2045                 return -1;
2046         }
2047
2048         while (fgets(buf, sizeof(buf), fp) != NULL) {
2049                 strretchomp(buf);
2050                 size += strlen(buf) + 2;
2051         }
2052
2053         if (ferror(fp)) {
2054                 FILE_OP_ERROR(file, "fgets");
2055                 size = -1;
2056         }
2057
2058         fclose(fp);
2059
2060         return size;
2061 }
2062
2063 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2064 {
2065         GStatBuf s;
2066
2067         if (file == NULL)
2068                 return FALSE;
2069
2070         if (g_stat(file, &s) < 0) {
2071                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2072                 return FALSE;
2073         }
2074
2075         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2076                 return TRUE;
2077
2078         return FALSE;
2079 }
2080
2081
2082 /* Test on whether FILE is a relative file name. This is
2083  * straightforward for Unix but more complex for Windows. */
2084 gboolean is_relative_filename(const gchar *file)
2085 {
2086         if (!file)
2087                 return TRUE;
2088 #ifdef G_OS_WIN32
2089         if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2090                 return FALSE; /* Prefixed with a hostname - this can't
2091                                * be a relative name. */
2092
2093         if ( ((*file >= 'a' && *file <= 'z')
2094               || (*file >= 'A' && *file <= 'Z'))
2095              && file[1] == ':')
2096                 file += 2;  /* Skip drive letter. */
2097
2098         return !(*file == '\\' || *file == '/');
2099 #else
2100         return !(*file == G_DIR_SEPARATOR);
2101 #endif
2102 }
2103
2104
2105 gboolean is_dir_exist(const gchar *dir)
2106 {
2107         if (dir == NULL)
2108                 return FALSE;
2109
2110         return g_file_test(dir, G_FILE_TEST_IS_DIR);
2111 }
2112
2113 gboolean is_file_entry_exist(const gchar *file)
2114 {
2115         if (file == NULL)
2116                 return FALSE;
2117
2118         return g_file_test(file, G_FILE_TEST_EXISTS);
2119 }
2120
2121 gboolean dirent_is_regular_file(struct dirent *d)
2122 {
2123 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2124         if (d->d_type == DT_REG)
2125                 return TRUE;
2126         else if (d->d_type != DT_UNKNOWN)
2127                 return FALSE;
2128 #endif
2129
2130         return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2131 }
2132
2133 gint change_dir(const gchar *dir)
2134 {
2135         gchar *prevdir = NULL;
2136
2137         if (debug_mode)
2138                 prevdir = g_get_current_dir();
2139
2140         if (g_chdir(dir) < 0) {
2141                 FILE_OP_ERROR(dir, "chdir");
2142                 if (debug_mode) g_free(prevdir);
2143                 return -1;
2144         } else if (debug_mode) {
2145                 gchar *cwd;
2146
2147                 cwd = g_get_current_dir();
2148                 if (strcmp(prevdir, cwd) != 0)
2149                         g_print("current dir: %s\n", cwd);
2150                 g_free(cwd);
2151                 g_free(prevdir);
2152         }
2153
2154         return 0;
2155 }
2156
2157 gint make_dir(const gchar *dir)
2158 {
2159         if (g_mkdir(dir, S_IRWXU) < 0) {
2160                 FILE_OP_ERROR(dir, "mkdir");
2161                 return -1;
2162         }
2163         if (g_chmod(dir, S_IRWXU) < 0)
2164                 FILE_OP_ERROR(dir, "chmod");
2165
2166         return 0;
2167 }
2168
2169 gint make_dir_hier(const gchar *dir)
2170 {
2171         gchar *parent_dir;
2172         const gchar *p;
2173
2174         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2175                 parent_dir = g_strndup(dir, p - dir);
2176                 if (*parent_dir != '\0') {
2177                         if (!is_dir_exist(parent_dir)) {
2178                                 if (make_dir(parent_dir) < 0) {
2179                                         g_free(parent_dir);
2180                                         return -1;
2181                                 }
2182                         }
2183                 }
2184                 g_free(parent_dir);
2185         }
2186
2187         if (!is_dir_exist(dir)) {
2188                 if (make_dir(dir) < 0)
2189                         return -1;
2190         }
2191
2192         return 0;
2193 }
2194
2195 gint remove_all_files(const gchar *dir)
2196 {
2197         GDir *dp;
2198         const gchar *dir_name;
2199         gchar *prev_dir;
2200
2201         prev_dir = g_get_current_dir();
2202
2203         if (g_chdir(dir) < 0) {
2204                 FILE_OP_ERROR(dir, "chdir");
2205                 g_free(prev_dir);
2206                 return -1;
2207         }
2208
2209         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2210                 g_warning("failed to open directory: %s", dir);
2211                 g_free(prev_dir);
2212                 return -1;
2213         }
2214
2215         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2216                 if (claws_unlink(dir_name) < 0)
2217                         FILE_OP_ERROR(dir_name, "unlink");
2218         }
2219
2220         g_dir_close(dp);
2221
2222         if (g_chdir(prev_dir) < 0) {
2223                 FILE_OP_ERROR(prev_dir, "chdir");
2224                 g_free(prev_dir);
2225                 return -1;
2226         }
2227
2228         g_free(prev_dir);
2229
2230         return 0;
2231 }
2232
2233 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2234 {
2235         GDir *dp;
2236         const gchar *dir_name;
2237         gchar *prev_dir;
2238         gint file_no;
2239
2240         if (first == last) {
2241                 /* Skip all the dir reading part. */
2242                 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2243                 if (is_dir_exist(filename)) {
2244                         /* a numbered directory with this name exists,
2245                          * remove the dot-file instead */
2246                         g_free(filename);
2247                         filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2248                 }
2249                 if (claws_unlink(filename) < 0) {
2250                         FILE_OP_ERROR(filename, "unlink");
2251                         g_free(filename);
2252                         return -1;
2253                 }
2254                 g_free(filename);
2255                 return 0;
2256         }
2257
2258         prev_dir = g_get_current_dir();
2259
2260         if (g_chdir(dir) < 0) {
2261                 FILE_OP_ERROR(dir, "chdir");
2262                 g_free(prev_dir);
2263                 return -1;
2264         }
2265
2266         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2267                 g_warning("failed to open directory: %s", dir);
2268                 g_free(prev_dir);
2269                 return -1;
2270         }
2271
2272         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2273                 file_no = to_number(dir_name);
2274                 if (file_no > 0 && first <= file_no && file_no <= last) {
2275                         if (is_dir_exist(dir_name)) {
2276                                 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2277                                 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2278                                         FILE_OP_ERROR(dot_file, "unlink");
2279                                 }
2280                                 g_free(dot_file);
2281                                 continue;
2282                         }
2283                         if (claws_unlink(dir_name) < 0)
2284                                 FILE_OP_ERROR(dir_name, "unlink");
2285                 }
2286         }
2287
2288         g_dir_close(dp);
2289
2290         if (g_chdir(prev_dir) < 0) {
2291                 FILE_OP_ERROR(prev_dir, "chdir");
2292                 g_free(prev_dir);
2293                 return -1;
2294         }
2295
2296         g_free(prev_dir);
2297
2298         return 0;
2299 }
2300
2301 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2302 {
2303         GDir *dp;
2304         const gchar *dir_name;
2305         gchar *prev_dir;
2306         gint file_no;
2307         GHashTable *wanted_files;
2308         GSList *cur;
2309         GError *error = NULL;
2310
2311         if (numberlist == NULL)
2312             return 0;
2313
2314         prev_dir = g_get_current_dir();
2315
2316         if (g_chdir(dir) < 0) {
2317                 FILE_OP_ERROR(dir, "chdir");
2318                 g_free(prev_dir);
2319                 return -1;
2320         }
2321
2322         if ((dp = g_dir_open(".", 0, &error)) == NULL) {
2323                 g_message("Couldn't open current directory: %s (%d).\n",
2324                                 error->message, error->code);
2325                 g_error_free(error);
2326                 g_free(prev_dir);
2327                 return -1;
2328         }
2329
2330         wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2331         for (cur = numberlist; cur != NULL; cur = cur->next) {
2332                 /* numberlist->data is expected to be GINT_TO_POINTER */
2333                 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2334         }
2335
2336         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2337                 file_no = to_number(dir_name);
2338                 if (is_dir_exist(dir_name))
2339                         continue;
2340                 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2341                         debug_print("removing unwanted file %d from %s\n", file_no, dir);
2342                         if (is_dir_exist(dir_name)) {
2343                                 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2344                                 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2345                                         FILE_OP_ERROR(dot_file, "unlink");
2346                                 }
2347                                 g_free(dot_file);
2348                                 continue;
2349                         }
2350                         if (claws_unlink(dir_name) < 0)
2351                                 FILE_OP_ERROR(dir_name, "unlink");
2352                 }
2353         }
2354
2355         g_dir_close(dp);
2356         g_hash_table_destroy(wanted_files);
2357
2358         if (g_chdir(prev_dir) < 0) {
2359                 FILE_OP_ERROR(prev_dir, "chdir");
2360                 g_free(prev_dir);
2361                 return -1;
2362         }
2363
2364         g_free(prev_dir);
2365
2366         return 0;
2367 }
2368
2369 gint remove_all_numbered_files(const gchar *dir)
2370 {
2371         return remove_numbered_files(dir, 0, UINT_MAX);
2372 }
2373
2374 gint remove_dir_recursive(const gchar *dir)
2375 {
2376         GStatBuf s;
2377         GDir *dp;
2378         const gchar *dir_name;
2379         gchar *prev_dir;
2380
2381         if (g_stat(dir, &s) < 0) {
2382                 FILE_OP_ERROR(dir, "stat");
2383                 if (ENOENT == errno) return 0;
2384                 return -(errno);
2385         }
2386
2387         if (!S_ISDIR(s.st_mode)) {
2388                 if (claws_unlink(dir) < 0) {
2389                         FILE_OP_ERROR(dir, "unlink");
2390                         return -(errno);
2391                 }
2392
2393                 return 0;
2394         }
2395
2396         prev_dir = g_get_current_dir();
2397         /* g_print("prev_dir = %s\n", prev_dir); */
2398
2399         if (!path_cmp(prev_dir, dir)) {
2400                 g_free(prev_dir);
2401                 if (g_chdir("..") < 0) {
2402                         FILE_OP_ERROR(dir, "chdir");
2403                         return -(errno);
2404                 }
2405                 prev_dir = g_get_current_dir();
2406         }
2407
2408         if (g_chdir(dir) < 0) {
2409                 FILE_OP_ERROR(dir, "chdir");
2410                 g_free(prev_dir);
2411                 return -(errno);
2412         }
2413
2414         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2415                 g_warning("failed to open directory: %s", dir);
2416                 g_chdir(prev_dir);
2417                 g_free(prev_dir);
2418                 return -(errno);
2419         }
2420
2421         /* remove all files in the directory */
2422         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2423                 /* g_print("removing %s\n", dir_name); */
2424
2425                 if (is_dir_exist(dir_name)) {
2426                         gint ret;
2427
2428                         if ((ret = remove_dir_recursive(dir_name)) < 0) {
2429                                 g_warning("can't remove directory: %s", dir_name);
2430                                 return ret;
2431                         }
2432                 } else {
2433                         if (claws_unlink(dir_name) < 0)
2434                                 FILE_OP_ERROR(dir_name, "unlink");
2435                 }
2436         }
2437
2438         g_dir_close(dp);
2439
2440         if (g_chdir(prev_dir) < 0) {
2441                 FILE_OP_ERROR(prev_dir, "chdir");
2442                 g_free(prev_dir);
2443                 return -(errno);
2444         }
2445
2446         g_free(prev_dir);
2447
2448         if (g_rmdir(dir) < 0) {
2449                 FILE_OP_ERROR(dir, "rmdir");
2450                 return -(errno);
2451         }
2452
2453         return 0;
2454 }
2455
2456 gint rename_force(const gchar *oldpath, const gchar *newpath)
2457 {
2458 #ifndef G_OS_UNIX
2459         if (!is_file_entry_exist(oldpath)) {
2460                 errno = ENOENT;
2461                 return -1;
2462         }
2463         if (is_file_exist(newpath)) {
2464                 if (claws_unlink(newpath) < 0)
2465                         FILE_OP_ERROR(newpath, "unlink");
2466         }
2467 #endif
2468         return g_rename(oldpath, newpath);
2469 }
2470
2471 /*
2472  * Append src file body to the tail of dest file.
2473  * Now keep_backup has no effects.
2474  */
2475 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2476 {
2477         FILE *src_fp, *dest_fp;
2478         gint n_read;
2479         gchar buf[BUFSIZ];
2480
2481         gboolean err = FALSE;
2482
2483         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2484                 FILE_OP_ERROR(src, "g_fopen");
2485                 return -1;
2486         }
2487
2488         if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2489                 FILE_OP_ERROR(dest, "g_fopen");
2490                 fclose(src_fp);
2491                 return -1;
2492         }
2493
2494         if (change_file_mode_rw(dest_fp, dest) < 0) {
2495                 FILE_OP_ERROR(dest, "chmod");
2496                 g_warning("can't change file mode: %s", dest);
2497         }
2498
2499         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2500                 if (n_read < sizeof(buf) && ferror(src_fp))
2501                         break;
2502                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2503                         g_warning("writing to %s failed.", dest);
2504                         fclose(dest_fp);
2505                         fclose(src_fp);
2506                         claws_unlink(dest);
2507                         return -1;
2508                 }
2509         }
2510
2511         if (ferror(src_fp)) {
2512                 FILE_OP_ERROR(src, "fread");
2513                 err = TRUE;
2514         }
2515         fclose(src_fp);
2516         if (fclose(dest_fp) == EOF) {
2517                 FILE_OP_ERROR(dest, "fclose");
2518                 err = TRUE;
2519         }
2520
2521         if (err) {
2522                 claws_unlink(dest);
2523                 return -1;
2524         }
2525
2526         return 0;
2527 }
2528
2529 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2530 {
2531         FILE *src_fp, *dest_fp;
2532         gint n_read;
2533         gchar buf[BUFSIZ];
2534         gchar *dest_bak = NULL;
2535         gboolean err = FALSE;
2536
2537         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2538                 FILE_OP_ERROR(src, "g_fopen");
2539                 return -1;
2540         }
2541         if (is_file_exist(dest)) {
2542                 dest_bak = g_strconcat(dest, ".bak", NULL);
2543                 if (rename_force(dest, dest_bak) < 0) {
2544                         FILE_OP_ERROR(dest, "rename");
2545                         fclose(src_fp);
2546                         g_free(dest_bak);
2547                         return -1;
2548                 }
2549         }
2550
2551         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2552                 FILE_OP_ERROR(dest, "g_fopen");
2553                 fclose(src_fp);
2554                 if (dest_bak) {
2555                         if (rename_force(dest_bak, dest) < 0)
2556                                 FILE_OP_ERROR(dest_bak, "rename");
2557                         g_free(dest_bak);
2558                 }
2559                 return -1;
2560         }
2561
2562         if (change_file_mode_rw(dest_fp, dest) < 0) {
2563                 FILE_OP_ERROR(dest, "chmod");
2564                 g_warning("can't change file mode: %s", dest);
2565         }
2566
2567         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2568                 if (n_read < sizeof(buf) && ferror(src_fp))
2569                         break;
2570                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2571                         g_warning("writing to %s failed.", dest);
2572                         fclose(dest_fp);
2573                         fclose(src_fp);
2574                         claws_unlink(dest);
2575                         if (dest_bak) {
2576                                 if (rename_force(dest_bak, dest) < 0)
2577                                         FILE_OP_ERROR(dest_bak, "rename");
2578                                 g_free(dest_bak);
2579                         }
2580                         return -1;
2581                 }
2582         }
2583
2584         if (ferror(src_fp)) {
2585                 FILE_OP_ERROR(src, "fread");
2586                 err = TRUE;
2587         }
2588         fclose(src_fp);
2589         if (fclose(dest_fp) == EOF) {
2590                 FILE_OP_ERROR(dest, "fclose");
2591                 err = TRUE;
2592         }
2593
2594         if (err) {
2595                 claws_unlink(dest);
2596                 if (dest_bak) {
2597                         if (rename_force(dest_bak, dest) < 0)
2598                                 FILE_OP_ERROR(dest_bak, "rename");
2599                         g_free(dest_bak);
2600                 }
2601                 return -1;
2602         }
2603
2604         if (keep_backup == FALSE && dest_bak)
2605                 claws_unlink(dest_bak);
2606
2607         g_free(dest_bak);
2608
2609         return 0;
2610 }
2611
2612 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2613 {
2614         if (overwrite == FALSE && is_file_exist(dest)) {
2615                 g_warning("move_file(): file %s already exists.", dest);
2616                 return -1;
2617         }
2618
2619         if (rename_force(src, dest) == 0) return 0;
2620
2621         if (EXDEV != errno) {
2622                 FILE_OP_ERROR(src, "rename");
2623                 return -1;
2624         }
2625
2626         if (copy_file(src, dest, FALSE) < 0) return -1;
2627
2628         claws_unlink(src);
2629
2630         return 0;
2631 }
2632
2633 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2634 {
2635         gint n_read;
2636         gint bytes_left, to_read;
2637         gchar buf[BUFSIZ];
2638
2639         if (fseek(fp, offset, SEEK_SET) < 0) {
2640                 perror("fseek");
2641                 return -1;
2642         }
2643
2644         bytes_left = length;
2645         to_read = MIN(bytes_left, sizeof(buf));
2646
2647         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2648                 if (n_read < to_read && ferror(fp))
2649                         break;
2650                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2651                         return -1;
2652                 }
2653                 bytes_left -= n_read;
2654                 if (bytes_left == 0)
2655                         break;
2656                 to_read = MIN(bytes_left, sizeof(buf));
2657         }
2658
2659         if (ferror(fp)) {
2660                 perror("fread");
2661                 return -1;
2662         }
2663
2664         return 0;
2665 }
2666
2667 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2668 {
2669         FILE *dest_fp;
2670         gboolean err = FALSE;
2671
2672         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2673                 FILE_OP_ERROR(dest, "g_fopen");
2674                 return -1;
2675         }
2676
2677         if (change_file_mode_rw(dest_fp, dest) < 0) {
2678                 FILE_OP_ERROR(dest, "chmod");
2679                 g_warning("can't change file mode: %s", dest);
2680         }
2681
2682         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2683                 err = TRUE;
2684
2685         if (!err && fclose(dest_fp) == EOF) {
2686                 FILE_OP_ERROR(dest, "fclose");
2687                 err = TRUE;
2688         }
2689
2690         if (err) {
2691                 g_warning("writing to %s failed.", dest);
2692                 claws_unlink(dest);
2693                 return -1;
2694         }
2695
2696         return 0;
2697 }
2698
2699 /* convert line endings into CRLF. If the last line doesn't end with
2700  * linebreak, add it.
2701  */
2702 gchar *canonicalize_str(const gchar *str)
2703 {
2704         const gchar *p;
2705         guint new_len = 0;
2706         gchar *out, *outp;
2707
2708         for (p = str; *p != '\0'; ++p) {
2709                 if (*p != '\r') {
2710                         ++new_len;
2711                         if (*p == '\n')
2712                                 ++new_len;
2713                 }
2714         }
2715         if (p == str || *(p - 1) != '\n')
2716                 new_len += 2;
2717
2718         out = outp = g_malloc(new_len + 1);
2719         for (p = str; *p != '\0'; ++p) {
2720                 if (*p != '\r') {
2721                         if (*p == '\n')
2722                                 *outp++ = '\r';
2723                         *outp++ = *p;
2724                 }
2725         }
2726         if (p == str || *(p - 1) != '\n') {
2727                 *outp++ = '\r';
2728                 *outp++ = '\n';
2729         }
2730         *outp = '\0';
2731
2732         return out;
2733 }
2734
2735 gint canonicalize_file(const gchar *src, const gchar *dest)
2736 {
2737         FILE *src_fp, *dest_fp;
2738         gchar buf[BUFFSIZE];
2739         gint len;
2740         gboolean err = FALSE;
2741         gboolean last_linebreak = FALSE;
2742
2743         if (src == NULL || dest == NULL)
2744                 return -1;
2745
2746         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2747                 FILE_OP_ERROR(src, "g_fopen");
2748                 return -1;
2749         }
2750
2751         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2752                 FILE_OP_ERROR(dest, "g_fopen");
2753                 fclose(src_fp);
2754                 return -1;
2755         }
2756
2757         if (change_file_mode_rw(dest_fp, dest) < 0) {
2758                 FILE_OP_ERROR(dest, "chmod");
2759                 g_warning("can't change file mode: %s", dest);
2760         }
2761
2762         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2763                 gint r = 0;
2764
2765                 len = strlen(buf);
2766                 if (len == 0) break;
2767                 last_linebreak = FALSE;
2768
2769                 if (buf[len - 1] != '\n') {
2770                         last_linebreak = TRUE;
2771                         r = fputs(buf, dest_fp);
2772                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2773                         r = fputs(buf, dest_fp);
2774                 } else {
2775                         if (len > 1) {
2776                                 r = fwrite(buf, 1, len - 1, dest_fp);
2777                                 if (r != (len -1))
2778                                         r = EOF;
2779                         }
2780                         if (r != EOF)
2781                                 r = fputs("\r\n", dest_fp);
2782                 }
2783
2784                 if (r == EOF) {
2785                         g_warning("writing to %s failed.", dest);
2786                         fclose(dest_fp);
2787                         fclose(src_fp);
2788                         claws_unlink(dest);
2789                         return -1;
2790                 }
2791         }
2792
2793         if (last_linebreak == TRUE) {
2794                 if (fputs("\r\n", dest_fp) == EOF)
2795                         err = TRUE;
2796         }
2797
2798         if (ferror(src_fp)) {
2799                 FILE_OP_ERROR(src, "fgets");
2800                 err = TRUE;
2801         }
2802         fclose(src_fp);
2803         if (fclose(dest_fp) == EOF) {
2804                 FILE_OP_ERROR(dest, "fclose");
2805                 err = TRUE;
2806         }
2807
2808         if (err) {
2809                 claws_unlink(dest);
2810                 return -1;
2811         }
2812
2813         return 0;
2814 }
2815
2816 gint canonicalize_file_replace(const gchar *file)
2817 {
2818         gchar *tmp_file;
2819
2820         tmp_file = get_tmp_file();
2821
2822         if (canonicalize_file(file, tmp_file) < 0) {
2823                 g_free(tmp_file);
2824                 return -1;
2825         }
2826
2827         if (move_file(tmp_file, file, TRUE) < 0) {
2828                 g_warning("can't replace file: %s", file);
2829                 claws_unlink(tmp_file);
2830                 g_free(tmp_file);
2831                 return -1;
2832         }
2833
2834         g_free(tmp_file);
2835         return 0;
2836 }
2837
2838 gchar *normalize_newlines(const gchar *str)
2839 {
2840         const gchar *p;
2841         gchar *out, *outp;
2842
2843         out = outp = g_malloc(strlen(str) + 1);
2844         for (p = str; *p != '\0'; ++p) {
2845                 if (*p == '\r') {
2846                         if (*(p + 1) != '\n')
2847                                 *outp++ = '\n';
2848                 } else
2849                         *outp++ = *p;
2850         }
2851
2852         *outp = '\0';
2853
2854         return out;
2855 }
2856
2857 gchar *get_outgoing_rfc2822_str(FILE *fp)
2858 {
2859         gchar buf[BUFFSIZE];
2860         GString *str;
2861         gchar *ret;
2862
2863         str = g_string_new(NULL);
2864
2865         /* output header part */
2866         while (fgets(buf, sizeof(buf), fp) != NULL) {
2867                 strretchomp(buf);
2868                 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2869                         gint next;
2870
2871                         for (;;) {
2872                                 next = fgetc(fp);
2873                                 if (next == EOF)
2874                                         break;
2875                                 else if (next != ' ' && next != '\t') {
2876                                         ungetc(next, fp);
2877                                         break;
2878                                 }
2879                                 if (fgets(buf, sizeof(buf), fp) == NULL)
2880                                         break;
2881                         }
2882                 } else {
2883                         g_string_append(str, buf);
2884                         g_string_append(str, "\r\n");
2885                         if (buf[0] == '\0')
2886                                 break;
2887                 }
2888         }
2889
2890         /* output body part */
2891         while (fgets(buf, sizeof(buf), fp) != NULL) {
2892                 strretchomp(buf);
2893                 if (buf[0] == '.')
2894                         g_string_append_c(str, '.');
2895                 g_string_append(str, buf);
2896                 g_string_append(str, "\r\n");
2897         }
2898
2899         ret = str->str;
2900         g_string_free(str, FALSE);
2901
2902         return ret;
2903 }
2904
2905 /*
2906  * Create a new boundary in a way that it is very unlikely that this
2907  * will occur in the following text.  It would be easy to ensure
2908  * uniqueness if everything is either quoted-printable or base64
2909  * encoded (note that conversion is allowed), but because MIME bodies
2910  * may be nested, it may happen that the same boundary has already
2911  * been used.
2912  *
2913  *   boundary := 0*69<bchars> bcharsnospace
2914  *   bchars := bcharsnospace / " "
2915  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2916  *                  "+" / "_" / "," / "-" / "." /
2917  *                  "/" / ":" / "=" / "?"
2918  *
2919  * some special characters removed because of buggy MTAs
2920  */
2921
2922 gchar *generate_mime_boundary(const gchar *prefix)
2923 {
2924         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2925                              "abcdefghijklmnopqrstuvwxyz"
2926                              "1234567890+_./=";
2927         gchar buf_uniq[24];
2928         gint i;
2929
2930         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2931                 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2932         buf_uniq[i] = '\0';
2933
2934         return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2935                                buf_uniq);
2936 }
2937
2938 gint change_file_mode_rw(FILE *fp, const gchar *file)
2939 {
2940 #if HAVE_FCHMOD
2941         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2942 #else
2943         return g_chmod(file, S_IRUSR|S_IWUSR);
2944 #endif
2945 }
2946
2947 FILE *my_tmpfile(void)
2948 {
2949         const gchar suffix[] = ".XXXXXX";
2950         const gchar *tmpdir;
2951         guint tmplen;
2952         const gchar *progname;
2953         guint proglen;
2954         gchar *fname;
2955         gint fd;
2956         FILE *fp;
2957 #ifndef G_OS_WIN32
2958         gchar buf[2]="\0";
2959 #endif
2960
2961         tmpdir = get_tmp_dir();
2962         tmplen = strlen(tmpdir);
2963         progname = g_get_prgname();
2964         if (progname == NULL)
2965                 progname = "claws-mail";
2966         proglen = strlen(progname);
2967         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2968                 return tmpfile());
2969
2970         memcpy(fname, tmpdir, tmplen);
2971         fname[tmplen] = G_DIR_SEPARATOR;
2972         memcpy(fname + tmplen + 1, progname, proglen);
2973         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2974
2975         fd = g_mkstemp(fname);
2976         if (fd < 0)
2977                 return tmpfile();
2978
2979 #ifndef G_OS_WIN32
2980         claws_unlink(fname);
2981         
2982         /* verify that we can write in the file after unlinking */
2983         if (write(fd, buf, 1) < 0) {
2984                 close(fd);
2985                 return tmpfile();
2986         }
2987         
2988 #endif
2989
2990         fp = fdopen(fd, "w+b");
2991         if (!fp)
2992                 close(fd);
2993         else {
2994                 rewind(fp);
2995                 return fp;
2996         }
2997
2998         return tmpfile();
2999 }
3000
3001 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3002 {
3003         int fd;
3004         *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3005         fd = g_mkstemp(*filename);
3006         if (fd < 0)
3007                 return NULL;
3008         return fdopen(fd, "w+");
3009 }
3010
3011 FILE *str_open_as_stream(const gchar *str)
3012 {
3013         FILE *fp;
3014         size_t len;
3015
3016         cm_return_val_if_fail(str != NULL, NULL);
3017
3018         fp = my_tmpfile();
3019         if (!fp) {
3020                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3021                 return NULL;
3022         }
3023
3024         len = strlen(str);
3025         if (len == 0) return fp;
3026
3027         if (fwrite(str, 1, len, fp) != len) {
3028                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3029                 fclose(fp);
3030                 return NULL;
3031         }
3032
3033         rewind(fp);
3034         return fp;
3035 }
3036
3037 gint str_write_to_file(const gchar *str, const gchar *file)
3038 {
3039         FILE *fp;
3040         size_t len;
3041
3042         cm_return_val_if_fail(str != NULL, -1);
3043         cm_return_val_if_fail(file != NULL, -1);
3044
3045         if ((fp = g_fopen(file, "wb")) == NULL) {
3046                 FILE_OP_ERROR(file, "g_fopen");
3047                 return -1;
3048         }
3049
3050         len = strlen(str);
3051         if (len == 0) {
3052                 fclose(fp);
3053                 return 0;
3054         }
3055
3056         if (fwrite(str, 1, len, fp) != len) {
3057                 FILE_OP_ERROR(file, "fwrite");
3058                 fclose(fp);
3059                 claws_unlink(file);
3060                 return -1;
3061         }
3062
3063         if (fclose(fp) == EOF) {
3064                 FILE_OP_ERROR(file, "fclose");
3065                 claws_unlink(file);
3066                 return -1;
3067         }
3068
3069         return 0;
3070 }
3071
3072 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3073 {
3074         GByteArray *array;
3075         guchar buf[BUFSIZ];
3076         gint n_read;
3077         gchar *str;
3078
3079         cm_return_val_if_fail(fp != NULL, NULL);
3080
3081         array = g_byte_array_new();
3082
3083         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3084                 if (n_read < sizeof(buf) && ferror(fp))
3085                         break;
3086                 g_byte_array_append(array, buf, n_read);
3087         }
3088
3089         if (ferror(fp)) {
3090                 FILE_OP_ERROR("file stream", "fread");
3091                 g_byte_array_free(array, TRUE);
3092                 return NULL;
3093         }
3094
3095         buf[0] = '\0';
3096         g_byte_array_append(array, buf, 1);
3097         str = (gchar *)array->data;
3098         g_byte_array_free(array, FALSE);
3099
3100         if (recode && !g_utf8_validate(str, -1, NULL)) {
3101                 const gchar *src_codeset, *dest_codeset;
3102                 gchar *tmp = NULL;
3103                 src_codeset = conv_get_locale_charset_str();
3104                 dest_codeset = CS_UTF_8;
3105                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3106                 g_free(str);
3107                 str = tmp;
3108         }
3109
3110         return str;
3111 }
3112
3113 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3114 {
3115         FILE *fp;
3116         gchar *str;
3117         GStatBuf s;
3118 #ifndef G_OS_WIN32
3119         gint fd, err;
3120         struct timeval timeout = {1, 0};
3121         fd_set fds;
3122         int fflags = 0;
3123 #endif
3124
3125         cm_return_val_if_fail(file != NULL, NULL);
3126
3127         if (g_stat(file, &s) != 0) {
3128                 FILE_OP_ERROR(file, "stat");
3129                 return NULL;
3130         }
3131         if (S_ISDIR(s.st_mode)) {
3132                 g_warning("%s: is a directory", file);
3133                 return NULL;
3134         }
3135
3136 #ifdef G_OS_WIN32
3137         fp = g_fopen (file, "rb");
3138         if (fp == NULL) {
3139                 FILE_OP_ERROR(file, "open");
3140                 return NULL;
3141         }
3142 #else     
3143         /* test whether the file is readable without blocking */
3144         fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3145         if (fd == -1) {
3146                 FILE_OP_ERROR(file, "open");
3147                 return NULL;
3148         }
3149
3150         FD_ZERO(&fds);
3151         FD_SET(fd, &fds);
3152
3153         /* allow for one second */
3154         err = select(fd+1, &fds, NULL, NULL, &timeout);
3155         if (err <= 0 || !FD_ISSET(fd, &fds)) {
3156                 if (err < 0) {
3157                         FILE_OP_ERROR(file, "select");
3158                 } else {
3159                         g_warning("%s: doesn't seem readable", file);
3160                 }
3161                 close(fd);
3162                 return NULL;
3163         }
3164         
3165         /* Now clear O_NONBLOCK */
3166         if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3167                 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3168                 close(fd);
3169                 return NULL;
3170         }
3171         if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3172                 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3173                 close(fd);
3174                 return NULL;
3175         }
3176         
3177         /* get the FILE pointer */
3178         fp = fdopen(fd, "rb");
3179
3180         if (fp == NULL) {
3181                 FILE_OP_ERROR(file, "fdopen");
3182                 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3183                 return NULL;
3184         }
3185 #endif
3186
3187         str = file_read_stream_to_str_full(fp, recode);
3188
3189         fclose(fp);
3190
3191         return str;
3192 }
3193
3194 gchar *file_read_to_str(const gchar *file)
3195 {
3196         return file_read_to_str_full(file, TRUE);
3197 }
3198 gchar *file_read_stream_to_str(FILE *fp)
3199 {
3200         return file_read_stream_to_str_full(fp, TRUE);
3201 }
3202
3203 gchar *file_read_to_str_no_recode(const gchar *file)
3204 {
3205         return file_read_to_str_full(file, FALSE);
3206 }
3207 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3208 {
3209         return file_read_stream_to_str_full(fp, FALSE);
3210 }
3211
3212 char *fgets_crlf(char *buf, int size, FILE *stream)
3213 {
3214         gboolean is_cr = FALSE;
3215         gboolean last_was_cr = FALSE;
3216         int c = 0;
3217         char *cs;
3218
3219         cs = buf;
3220         while (--size > 0 && (c = getc(stream)) != EOF)
3221         {
3222                 *cs++ = c;
3223                 is_cr = (c == '\r');
3224                 if (c == '\n') {
3225                         break;
3226                 }
3227                 if (last_was_cr) {
3228                         *(--cs) = '\n';
3229                         cs++;
3230                         ungetc(c, stream);
3231                         break;
3232                 }
3233                 last_was_cr = is_cr;
3234         }
3235         if (c == EOF && cs == buf)
3236                 return NULL;
3237
3238         *cs = '\0';
3239
3240         return buf;     
3241 }
3242
3243 static gint execute_async(gchar *const argv[])
3244 {
3245         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3246
3247         if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3248                           NULL, NULL, NULL, FALSE) == FALSE) {
3249                 g_warning("couldn't execute command: %s", argv[0]);
3250                 return -1;
3251         }
3252
3253         return 0;
3254 }
3255
3256 static gint execute_sync(gchar *const argv[])
3257 {
3258         gint status;
3259
3260         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3261
3262 #ifdef G_OS_UNIX
3263         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3264                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3265                 g_warning("couldn't execute command: %s", argv[0]);
3266                 return -1;
3267         }
3268
3269         if (WIFEXITED(status))
3270                 return WEXITSTATUS(status);
3271         else
3272                 return -1;
3273 #else
3274         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH| 
3275                          G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3276                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3277                 g_warning("couldn't execute command: %s", argv[0]);
3278                 return -1;
3279         }
3280
3281         return status;
3282 #endif
3283 }
3284
3285 gint execute_command_line(const gchar *cmdline, gboolean async)
3286 {
3287         gchar **argv;
3288         gint ret;
3289
3290         debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3291
3292         argv = strsplit_with_quote(cmdline, " ", 0);
3293
3294         if (async)
3295                 ret = execute_async(argv);
3296         else
3297                 ret = execute_sync(argv);
3298
3299         g_strfreev(argv);
3300
3301         return ret;
3302 }
3303
3304 gchar *get_command_output(const gchar *cmdline)
3305 {
3306         gchar *child_stdout;
3307         gint status;
3308
3309         cm_return_val_if_fail(cmdline != NULL, NULL);
3310
3311         debug_print("get_command_output(): executing: %s\n", cmdline);
3312
3313         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3314                                       NULL) == FALSE) {
3315                 g_warning("couldn't execute command: %s", cmdline);
3316                 return NULL;
3317         }
3318
3319         return child_stdout;
3320 }
3321
3322 static gint is_unchanged_uri_char(char c)
3323 {
3324         switch (c) {
3325                 case '(':
3326                 case ')':
3327                         return 0;
3328                 default:
3329                         return 1;
3330         }
3331 }
3332
3333 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3334 {
3335         int i;
3336         int k;
3337
3338         k = 0;
3339         for(i = 0; i < strlen(uri) ; i++) {
3340                 if (is_unchanged_uri_char(uri[i])) {
3341                         if (k + 2 >= bufsize)
3342                                 break;
3343                         encoded_uri[k++] = uri[i];
3344                 }
3345                 else {
3346                         char * hexa = "0123456789ABCDEF";
3347
3348                         if (k + 4 >= bufsize)
3349                                 break;
3350                         encoded_uri[k++] = '%';
3351                         encoded_uri[k++] = hexa[uri[i] / 16];
3352                         encoded_uri[k++] = hexa[uri[i] % 16];
3353                 }
3354         }
3355         encoded_uri[k] = 0;
3356 }
3357
3358 gint open_uri(const gchar *uri, const gchar *cmdline)
3359 {
3360
3361 #ifndef G_OS_WIN32
3362         gchar buf[BUFFSIZE];
3363         gchar *p;
3364         gchar encoded_uri[BUFFSIZE];
3365         cm_return_val_if_fail(uri != NULL, -1);
3366
3367         /* an option to choose whether to use encode_uri or not ? */
3368         encode_uri(encoded_uri, BUFFSIZE, uri);
3369
3370         if (cmdline &&
3371             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3372             !strchr(p + 2, '%'))
3373                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3374         else {
3375                 if (cmdline)
3376                         g_warning("Open URI command-line is invalid "
3377                                   "(there must be only one '%%s'): %s",
3378                                   cmdline);
3379                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3380         }
3381
3382         execute_command_line(buf, TRUE);
3383 #else
3384         ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3385 #endif
3386         return 0;
3387 }
3388
3389 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3390 {
3391         gchar buf[BUFFSIZE];
3392         gchar *p;
3393
3394         cm_return_val_if_fail(filepath != NULL, -1);
3395
3396         if (cmdline &&
3397             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3398             !strchr(p + 2, '%'))
3399                 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3400         else {
3401                 if (cmdline)
3402                         g_warning("Open Text Editor command-line is invalid "
3403                                   "(there must be only one '%%s'): %s",
3404                                   cmdline);
3405                 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3406         }
3407
3408         execute_command_line(buf, TRUE);
3409
3410         return 0;
3411 }
3412
3413 time_t remote_tzoffset_sec(const gchar *zone)
3414 {
3415         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3416         gchar zone3[4];
3417         gchar *p;
3418         gchar c;
3419         gint iustz;
3420         gint offset;
3421         time_t remoteoffset;
3422
3423         strncpy(zone3, zone, 3);
3424         zone3[3] = '\0';
3425         remoteoffset = 0;
3426
3427         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3428             (c == '+' || c == '-')) {
3429                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3430                 if (c == '-')
3431                         remoteoffset = -remoteoffset;
3432         } else if (!strncmp(zone, "UT" , 2) ||
3433                    !strncmp(zone, "GMT", 3)) {
3434                 remoteoffset = 0;
3435         } else if (strlen(zone3) == 3) {
3436                 for (p = ustzstr; *p != '\0'; p += 3) {
3437                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3438                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3439                                 remoteoffset = iustz * 3600;
3440                                 break;
3441                         }
3442                 }
3443                 if (*p == '\0')
3444                         return -1;
3445         } else if (strlen(zone3) == 1) {
3446                 switch (zone[0]) {
3447                 case 'Z': remoteoffset =   0; break;
3448                 case 'A': remoteoffset =  -1; break;
3449                 case 'B': remoteoffset =  -2; break;
3450                 case 'C': remoteoffset =  -3; break;
3451                 case 'D': remoteoffset =  -4; break;
3452                 case 'E': remoteoffset =  -5; break;
3453                 case 'F': remoteoffset =  -6; break;
3454                 case 'G': remoteoffset =  -7; break;
3455                 case 'H': remoteoffset =  -8; break;
3456                 case 'I': remoteoffset =  -9; break;
3457                 case 'K': remoteoffset = -10; break; /* J is not used */
3458                 case 'L': remoteoffset = -11; break;
3459                 case 'M': remoteoffset = -12; break;
3460                 case 'N': remoteoffset =   1; break;
3461                 case 'O': remoteoffset =   2; break;
3462                 case 'P': remoteoffset =   3; break;
3463                 case 'Q': remoteoffset =   4; break;
3464                 case 'R': remoteoffset =   5; break;
3465                 case 'S': remoteoffset =   6; break;
3466                 case 'T': remoteoffset =   7; break;
3467                 case 'U': remoteoffset =   8; break;
3468                 case 'V': remoteoffset =   9; break;
3469                 case 'W': remoteoffset =  10; break;
3470                 case 'X': remoteoffset =  11; break;
3471                 case 'Y': remoteoffset =  12; break;
3472                 default:  remoteoffset =   0; break;
3473                 }
3474                 remoteoffset = remoteoffset * 3600;
3475         } else
3476                 return -1;
3477
3478         return remoteoffset;
3479 }
3480
3481 time_t tzoffset_sec(time_t *now)
3482 {
3483         struct tm gmt, *lt;
3484         gint off;
3485         struct tm buf1, buf2;
3486 #ifdef G_OS_WIN32
3487         if (now && *now < 0)
3488                 return 0;
3489 #endif  
3490         gmt = *gmtime_r(now, &buf1);
3491         lt = localtime_r(now, &buf2);
3492
3493         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3494
3495         if (lt->tm_year < gmt.tm_year)
3496                 off -= 24 * 60;
3497         else if (lt->tm_year > gmt.tm_year)
3498                 off += 24 * 60;
3499         else if (lt->tm_yday < gmt.tm_yday)
3500                 off -= 24 * 60;
3501         else if (lt->tm_yday > gmt.tm_yday)
3502                 off += 24 * 60;
3503
3504         if (off >= 24 * 60)             /* should be impossible */
3505                 off = 23 * 60 + 59;     /* if not, insert silly value */
3506         if (off <= -24 * 60)
3507                 off = -(23 * 60 + 59);
3508
3509         return off * 60;
3510 }
3511
3512 /* calculate timezone offset */
3513 gchar *tzoffset(time_t *now)
3514 {
3515         static gchar offset_string[6];
3516         struct tm gmt, *lt;
3517         gint off;
3518         gchar sign = '+';
3519         struct tm buf1, buf2;
3520 #ifdef G_OS_WIN32
3521         if (now && *now < 0)
3522                 return 0;
3523 #endif
3524         gmt = *gmtime_r(now, &buf1);
3525         lt = localtime_r(now, &buf2);
3526
3527         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3528
3529         if (lt->tm_year < gmt.tm_year)
3530                 off -= 24 * 60;
3531         else if (lt->tm_year > gmt.tm_year)
3532                 off += 24 * 60;
3533         else if (lt->tm_yday < gmt.tm_yday)
3534                 off -= 24 * 60;
3535         else if (lt->tm_yday > gmt.tm_yday)
3536                 off += 24 * 60;
3537
3538         if (off < 0) {
3539                 sign = '-';
3540                 off = -off;
3541         }
3542
3543         if (off >= 24 * 60)             /* should be impossible */
3544                 off = 23 * 60 + 59;     /* if not, insert silly value */
3545
3546         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3547
3548         return offset_string;
3549 }
3550
3551 void get_rfc822_date(gchar *buf, gint len)
3552 {
3553         struct tm *lt;
3554         time_t t;
3555         gchar day[4], mon[4];
3556         gint dd, hh, mm, ss, yyyy;
3557         struct tm buf1;
3558         gchar buf2[BUFFSIZE];
3559
3560         t = time(NULL);
3561         lt = localtime_r(&t, &buf1);
3562
3563         sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3564                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3565
3566         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3567                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3568 }
3569
3570 void debug_set_mode(gboolean mode)
3571 {
3572         debug_mode = mode;
3573 }
3574
3575 gboolean debug_get_mode(void)
3576 {
3577         return debug_mode;
3578 }
3579
3580 void debug_print_real(const gchar *format, ...)
3581 {
3582         va_list args;
3583         gchar buf[BUFFSIZE];
3584
3585         if (!debug_mode) return;
3586
3587         va_start(args, format);
3588         g_vsnprintf(buf, sizeof(buf), format, args);
3589         va_end(args);
3590
3591         g_print("%s", buf);
3592 }
3593
3594
3595 const char * debug_srcname(const char *file)
3596 {
3597         const char *s = strrchr (file, '/');
3598         return s? s+1:file;
3599 }
3600
3601
3602 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3603 {
3604         if (subject == NULL)
3605                 subject = "";
3606         else
3607                 subject += subject_get_prefix_length(subject);
3608
3609