51729821fa8ca1ecf214bad94ffd5e74892e65ac
[claws.git] / src / common / utils.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2016 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 #ifndef G_OS_WIN32
53 #include <sys/socket.h>
54 #endif
55
56 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
57 #  include <wchar.h>
58 #  include <wctype.h>
59 #endif
60 #include <stdlib.h>
61 #include <sys/stat.h>
62 #include <unistd.h>
63 #include <stdarg.h>
64 #include <sys/types.h>
65 #if HAVE_SYS_WAIT_H
66 #  include <sys/wait.h>
67 #endif
68 #include <dirent.h>
69 #include <time.h>
70 #include <regex.h>
71
72 #ifdef G_OS_UNIX
73 #include <sys/utsname.h>
74 #endif
75
76 #include <fcntl.h>
77
78 #ifdef G_OS_WIN32
79 #  include <direct.h>
80 #  include <io.h>
81 #  include <w32lib.h>
82 #endif
83
84 #include "utils.h"
85 #include "socket.h"
86 #include "../codeconv.h"
87 #include "tlds.h"
88
89 #define BUFFSIZE        8192
90
91 static gboolean debug_mode = FALSE;
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_full(GList *list)
138 {
139         g_list_free_full(list, (GDestroyNotify)g_free);
140 }
141
142 void slist_free_strings_full(GSList *list)
143 {
144         g_slist_free_full(list, (GDestroyNotify)g_free);
145 }
146
147 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
148 {
149         g_free(key);
150 }
151
152 void hash_free_strings(GHashTable *table)
153 {
154         g_hash_table_foreach(table, hash_free_strings_func, NULL);
155 }
156
157 gint str_case_equal(gconstpointer v, gconstpointer v2)
158 {
159         return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
160 }
161
162 guint str_case_hash(gconstpointer key)
163 {
164         const gchar *p = key;
165         guint h = *p;
166
167         if (h) {
168                 h = g_ascii_tolower(h);
169                 for (p += 1; *p != '\0'; p++)
170                         h = (h << 5) - h + g_ascii_tolower(*p);
171         }
172
173         return h;
174 }
175
176 void ptr_array_free_strings(GPtrArray *array)
177 {
178         gint i;
179         gchar *str;
180
181         cm_return_if_fail(array != NULL);
182
183         for (i = 0; i < array->len; i++) {
184                 str = g_ptr_array_index(array, i);
185                 g_free(str);
186         }
187 }
188
189 gint to_number(const gchar *nstr)
190 {
191         register const gchar *p;
192
193         if (*nstr == '\0') return -1;
194
195         for (p = nstr; *p != '\0'; p++)
196                 if (!g_ascii_isdigit(*p)) return -1;
197
198         return atoi(nstr);
199 }
200
201 /* convert integer into string,
202    nstr must be not lower than 11 characters length */
203 gchar *itos_buf(gchar *nstr, gint n)
204 {
205         g_snprintf(nstr, 11, "%d", n);
206         return nstr;
207 }
208
209 /* convert integer into string */
210 gchar *itos(gint n)
211 {
212         static gchar nstr[11];
213
214         return itos_buf(nstr, n);
215 }
216
217 #define divide(num,divisor,i,d)         \
218 {                                       \
219         i = num >> divisor;             \
220         d = num & ((1<<divisor)-1);     \
221         d = (d*100) >> divisor;         \
222 }
223
224
225 /*!
226  * \brief Convert a given size in bytes in a human-readable string
227  *
228  * \param size  The size expressed in bytes to convert in string
229  * \return      The string that respresents the size in an human-readable way
230  */
231 gchar *to_human_readable(goffset size)
232 {
233         static gchar str[14];
234         static gchar *b_format = NULL, *kb_format = NULL, 
235                      *mb_format = NULL, *gb_format = NULL;
236         register int t = 0, r = 0;
237         if (b_format == NULL) {
238                 b_format  = _("%dB");
239                 kb_format = _("%d.%02dKB");
240                 mb_format = _("%d.%02dMB");
241                 gb_format = _("%.2fGB");
242         }
243         
244         if (size < (goffset)1024) {
245                 g_snprintf(str, sizeof(str), b_format, (gint)size);
246                 return str;
247         } else if (size >> 10 < (goffset)1024) {
248                 divide(size, 10, t, r);
249                 g_snprintf(str, sizeof(str), kb_format, t, r);
250                 return str;
251         } else if (size >> 20 < (goffset)1024) {
252                 divide(size, 20, t, r);
253                 g_snprintf(str, sizeof(str), mb_format, t, r);
254                 return str;
255         } else {
256                 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
257                 return str;
258         }
259 }
260
261 /* strcmp with NULL-checking */
262 gint strcmp2(const gchar *s1, const gchar *s2)
263 {
264         if (s1 == NULL || s2 == NULL)
265                 return -1;
266         else
267                 return strcmp(s1, s2);
268 }
269 /* strstr with NULL-checking */
270 gchar *strstr2(const gchar *s1, const gchar *s2)
271 {
272         if (s1 == NULL || s2 == NULL)
273                 return NULL;
274         else
275                 return strstr(s1, s2);
276 }
277 /* compare paths */
278 gint path_cmp(const gchar *s1, const gchar *s2)
279 {
280         gint len1, len2;
281         int rc;
282 #ifdef G_OS_WIN32
283         gchar *s1buf, *s2buf;
284 #endif
285
286         if (s1 == NULL || s2 == NULL) return -1;
287         if (*s1 == '\0' || *s2 == '\0') return -1;
288
289 #ifdef G_OS_WIN32
290         s1buf = g_strdup (s1);
291         s2buf = g_strdup (s2);
292         subst_char (s1buf, '/', G_DIR_SEPARATOR);
293         subst_char (s2buf, '/', G_DIR_SEPARATOR);
294         s1 = s1buf;
295         s2 = s2buf;
296 #endif /* !G_OS_WIN32 */
297
298         len1 = strlen(s1);
299         len2 = strlen(s2);
300
301         if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
302         if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
303
304         rc = strncmp(s1, s2, MAX(len1, len2));
305 #ifdef G_OS_WIN32
306         g_free (s1buf);
307         g_free (s2buf);
308 #endif /* !G_OS_WIN32 */
309         return rc;
310 }
311
312 /* remove trailing return code */
313 gchar *strretchomp(gchar *str)
314 {
315         register gchar *s;
316
317         if (!*str) return str;
318
319         for (s = str + strlen(str) - 1;
320              s >= str && (*s == '\n' || *s == '\r');
321              s--)
322                 *s = '\0';
323
324         return str;
325 }
326
327 /* remove trailing character */
328 gchar *strtailchomp(gchar *str, gchar tail_char)
329 {
330         register gchar *s;
331
332         if (!*str) return str;
333         if (tail_char == '\0') return str;
334
335         for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
336                 *s = '\0';
337
338         return str;
339 }
340
341 /* remove CR (carriage return) */
342 gchar *strcrchomp(gchar *str)
343 {
344         register gchar *s;
345
346         if (!*str) return str;
347
348         s = str + strlen(str) - 1;
349         if (*s == '\n' && s > str && *(s - 1) == '\r') {
350                 *(s - 1) = '\n';
351                 *s = '\0';
352         }
353
354         return str;
355 }
356
357 gint file_strip_crs(const gchar *file)
358 {
359         FILE *fp = NULL, *outfp = NULL;
360         gchar buf[4096];
361         gchar *out = get_tmp_file();
362         if (file == NULL)
363                 goto freeout;
364
365         fp = g_fopen(file, "rb");
366         if (!fp)
367                 goto freeout;
368
369         outfp = g_fopen(out, "wb");
370         if (!outfp) {
371                 fclose(fp);
372                 goto freeout;
373         }
374
375         while (fgets(buf, sizeof (buf), fp) != NULL) {
376                 strcrchomp(buf);
377                 if (fputs(buf, outfp) == EOF) {
378                         fclose(fp);
379                         fclose(outfp);
380                         goto unlinkout;
381                 }
382         }
383
384         fclose(fp);
385         if (fclose(outfp) == EOF) {
386                 goto unlinkout;
387         }
388         
389         if (move_file(out, file, TRUE) < 0)
390                 goto unlinkout;
391         
392         g_free(out);
393         return 0;
394 unlinkout:
395         claws_unlink(out);
396 freeout:
397         g_free(out);
398         return -1;
399 }
400
401 /* Similar to `strstr' but this function ignores the case of both strings.  */
402 gchar *strcasestr(const gchar *haystack, const gchar *needle)
403 {
404         size_t haystack_len = strlen(haystack);
405
406         return strncasestr(haystack, haystack_len, needle);
407 }
408
409 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
410 {
411         register size_t needle_len;
412
413         needle_len   = strlen(needle);
414
415         if (haystack_len < needle_len || needle_len == 0)
416                 return NULL;
417
418         while (haystack_len >= needle_len) {
419                 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
420                         return (gchar *)haystack;
421                 else {
422                         haystack++;
423                         haystack_len--;
424                 }
425         }
426
427         return NULL;
428 }
429
430 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
431                    gconstpointer needle, size_t needlelen)
432 {
433         const gchar *haystack_ = (const gchar *)haystack;
434         const gchar *needle_ = (const gchar *)needle;
435         const gchar *haystack_cur = (const gchar *)haystack;
436         size_t haystack_left = haystacklen;
437
438         if (needlelen == 1)
439                 return memchr(haystack_, *needle_, haystacklen);
440
441         while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
442                != NULL) {
443                 if (haystacklen - (haystack_cur - haystack_) < needlelen)
444                         break;
445                 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
446                         return (gpointer)haystack_cur;
447                 else{
448                         haystack_cur++;
449                         haystack_left = haystacklen - (haystack_cur - haystack_);
450                 }
451         }
452
453         return NULL;
454 }
455
456 /* Copy no more than N characters of SRC to DEST, with NULL terminating.  */
457 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
458 {
459         register const gchar *s = src;
460         register gchar *d = dest;
461
462         while (--n && *s)
463                 *d++ = *s++;
464         *d = '\0';
465
466         return dest;
467 }
468
469
470 /* Examine if next block is non-ASCII string */
471 gboolean is_next_nonascii(const gchar *s)
472 {
473         const gchar *p;
474
475         /* skip head space */
476         for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
477                 ;
478         for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
479                 if (*(guchar *)p > 127 || *(guchar *)p < 32)
480                         return TRUE;
481         }
482
483         return FALSE;
484 }
485
486 gint get_next_word_len(const gchar *s)
487 {
488         gint len = 0;
489
490         for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
491                 ;
492
493         return len;
494 }
495
496 static void trim_subject_for_compare(gchar *str)
497 {
498         gchar *srcp;
499
500         eliminate_parenthesis(str, '[', ']');
501         eliminate_parenthesis(str, '(', ')');
502         g_strstrip(str);
503
504         srcp = str + subject_get_prefix_length(str);
505         if (srcp != str)
506                 memmove(str, srcp, strlen(srcp) + 1);
507 }
508
509 static void trim_subject_for_sort(gchar *str)
510 {
511         gchar *srcp;
512
513         g_strstrip(str);
514
515         srcp = str + subject_get_prefix_length(str);
516         if (srcp != str)
517                 memmove(str, srcp, strlen(srcp) + 1);
518 }
519
520 /* compare subjects */
521 gint subject_compare(const gchar *s1, const gchar *s2)
522 {
523         gchar *str1, *str2;
524
525         if (!s1 || !s2) return -1;
526         if (!*s1 || !*s2) return -1;
527
528         Xstrdup_a(str1, s1, return -1);
529         Xstrdup_a(str2, s2, return -1);
530
531         trim_subject_for_compare(str1);
532         trim_subject_for_compare(str2);
533
534         if (!*str1 || !*str2) return -1;
535
536         return strcmp(str1, str2);
537 }
538
539 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
540 {
541         gchar *str1, *str2;
542
543         if (!s1 || !s2) return -1;
544
545         Xstrdup_a(str1, s1, return -1);
546         Xstrdup_a(str2, s2, return -1);
547
548         trim_subject_for_sort(str1);
549         trim_subject_for_sort(str2);
550
551         return g_utf8_collate(str1, str2);
552 }
553
554 void trim_subject(gchar *str)
555 {
556         register gchar *srcp;
557         gchar op, cl;
558         gint in_brace;
559
560         g_strstrip(str);
561
562         srcp = str + subject_get_prefix_length(str);
563
564         if (*srcp == '[') {
565                 op = '[';
566                 cl = ']';
567         } else if (*srcp == '(') {
568                 op = '(';
569                 cl = ')';
570         } else
571                 op = 0;
572
573         if (op) {
574                 ++srcp;
575                 in_brace = 1;
576                 while (*srcp) {
577                         if (*srcp == op)
578                                 in_brace++;
579                         else if (*srcp == cl)
580                                 in_brace--;
581                         srcp++;
582                         if (in_brace == 0)
583                                 break;
584                 }
585         }
586         while (g_ascii_isspace(*srcp)) srcp++;
587         memmove(str, srcp, strlen(srcp) + 1);
588 }
589
590 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
591 {
592         register gchar *srcp, *destp;
593         gint in_brace;
594
595         destp = str;
596
597         while ((destp = strchr(destp, op))) {
598                 in_brace = 1;
599                 srcp = destp + 1;
600                 while (*srcp) {
601                         if (*srcp == op)
602                                 in_brace++;
603                         else if (*srcp == cl)
604                                 in_brace--;
605                         srcp++;
606                         if (in_brace == 0)
607                                 break;
608                 }
609                 while (g_ascii_isspace(*srcp)) srcp++;
610                 memmove(destp, srcp, strlen(srcp) + 1);
611         }
612 }
613
614 void extract_parenthesis(gchar *str, gchar op, gchar cl)
615 {
616         register gchar *srcp, *destp;
617         gint in_brace;
618
619         destp = str;
620
621         while ((srcp = strchr(destp, op))) {
622                 if (destp > str)
623                         *destp++ = ' ';
624                 memmove(destp, srcp + 1, strlen(srcp));
625                 in_brace = 1;
626                 while(*destp) {
627                         if (*destp == op)
628                                 in_brace++;
629                         else if (*destp == cl)
630                                 in_brace--;
631
632                         if (in_brace == 0)
633                                 break;
634
635                         destp++;
636                 }
637         }
638         *destp = '\0';
639 }
640
641 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
642                                          gchar op, gchar cl)
643 {
644         register gchar *srcp, *destp;
645         gint in_brace;
646         gboolean in_quote = FALSE;
647
648         destp = str;
649
650         while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
651                 if (destp > str)
652                         *destp++ = ' ';
653                 memmove(destp, srcp + 1, strlen(srcp));
654                 in_brace = 1;
655                 while(*destp) {
656                         if (*destp == op && !in_quote)
657                                 in_brace++;
658                         else if (*destp == cl && !in_quote)
659                                 in_brace--;
660                         else if (*destp == quote_chr)
661                                 in_quote ^= TRUE;
662
663                         if (in_brace == 0)
664                                 break;
665
666                         destp++;
667                 }
668         }
669         *destp = '\0';
670 }
671
672 void extract_quote(gchar *str, gchar quote_chr)
673 {
674         register gchar *p;
675
676         if ((str = strchr(str, quote_chr))) {
677                 p = str;
678                 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
679                         memmove(p - 1, p, strlen(p) + 1);
680                         p--;
681                 }
682                 if(p) {
683                         *p = '\0';
684                         memmove(str, str + 1, p - str);
685                 }
686         }
687 }
688
689 /* Returns a newly allocated string with all quote_chr not at the beginning
690    or the end of str escaped with '\' or the given str if not required. */
691 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
692 {
693         register gchar *p, *q;
694         gchar *qstr;
695         int k = 0, l = 0;
696
697         if (str == NULL || *str == '\0')
698                 return str;
699
700         /* search for unescaped quote_chr */
701         p = str;
702         if (*p == quote_chr)
703                 ++p, ++l;
704         while (*p) {
705                 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
706                         ++k;
707                 ++p, ++l;
708         }
709         if (!k) /* nothing to escape */
710                 return str;
711
712         /* unescaped quote_chr found */
713         qstr = g_malloc(l + k + 1);
714         p = str;
715         q = qstr;
716         if (*p == quote_chr) {
717                 *q = quote_chr;
718                 ++p, ++q;
719         }
720         while (*p) {
721                 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
722                         *q++ = '\\';
723                 *q++ = *p++;
724         }
725         *q = '\0';
726
727         return qstr;
728 }
729
730 void eliminate_address_comment(gchar *str)
731 {
732         register gchar *srcp, *destp;
733         gint in_brace;
734
735         destp = str;
736
737         while ((destp = strchr(destp, '"'))) {
738                 if ((srcp = strchr(destp + 1, '"'))) {
739                         srcp++;
740                         if (*srcp == '@') {
741                                 destp = srcp + 1;
742                         } else {
743                                 while (g_ascii_isspace(*srcp)) srcp++;
744                                 memmove(destp, srcp, strlen(srcp) + 1);
745                         }
746                 } else {
747                         *destp = '\0';
748                         break;
749                 }
750         }
751
752         destp = str;
753
754         while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
755                 in_brace = 1;
756                 srcp = destp + 1;
757                 while (*srcp) {
758                         if (*srcp == '(')
759                                 in_brace++;
760                         else if (*srcp == ')')
761                                 in_brace--;
762                         srcp++;
763                         if (in_brace == 0)
764                                 break;
765                 }
766                 while (g_ascii_isspace(*srcp)) srcp++;
767                 memmove(destp, srcp, strlen(srcp) + 1);
768         }
769 }
770
771 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
772 {
773         gboolean in_quote = FALSE;
774
775         while (*str) {
776                 if (*str == c && !in_quote)
777                         return (gchar *)str;
778                 if (*str == quote_chr)
779                         in_quote ^= TRUE;
780                 str++;
781         }
782
783         return NULL;
784 }
785
786 void extract_address(gchar *str)
787 {
788         cm_return_if_fail(str != NULL);
789         eliminate_address_comment(str);
790         if (strchr_with_skip_quote(str, '"', '<'))
791                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
792         g_strstrip(str);
793 }
794
795 void extract_list_id_str(gchar *str)
796 {
797         if (strchr_with_skip_quote(str, '"', '<'))
798                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
799         g_strstrip(str);
800 }
801
802 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
803 {
804         gchar *work;
805         gchar *workp;
806
807         if (!str) return addr_list;
808
809         Xstrdup_a(work, str, return addr_list);
810
811         if (removecomments)
812                 eliminate_address_comment(work);
813         workp = work;
814
815         while (workp && *workp) {
816                 gchar *p, *next;
817
818                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
819                         *p = '\0';
820                         next = p + 1;
821                 } else
822                         next = NULL;
823
824                 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
825                         extract_parenthesis_with_skip_quote
826                                 (workp, '"', '<', '>');
827
828                 g_strstrip(workp);
829                 if (*workp)
830                         addr_list = g_slist_append(addr_list, g_strdup(workp));
831
832                 workp = next;
833         }
834
835         return addr_list;
836 }
837
838 GSList *address_list_append(GSList *addr_list, const gchar *str)
839 {
840         return address_list_append_real(addr_list, str, TRUE);
841 }
842
843 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
844 {
845         return address_list_append_real(addr_list, str, FALSE);
846 }
847
848 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
849 {
850         const gchar *strp;
851
852         if (!str) return msgid_list;
853         strp = str;
854
855         while (strp && *strp) {
856                 const gchar *start, *end;
857                 gchar *msgid;
858
859                 if ((start = strchr(strp, '<')) != NULL) {
860                         end = strchr(start + 1, '>');
861                         if (!end) break;
862                 } else
863                         break;
864
865                 msgid = g_strndup(start + 1, end - start - 1);
866                 g_strstrip(msgid);
867                 if (*msgid)
868                         msgid_list = g_slist_prepend(msgid_list, msgid);
869                 else
870                         g_free(msgid);
871
872                 strp = end + 1;
873         }
874
875         return msgid_list;
876 }
877
878 GSList *references_list_append(GSList *msgid_list, const gchar *str)
879 {
880         GSList *list;
881
882         list = references_list_prepend(NULL, str);
883         list = g_slist_reverse(list);
884         msgid_list = g_slist_concat(msgid_list, list);
885
886         return msgid_list;
887 }
888
889 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
890 {
891         gchar *work;
892         gchar *workp;
893
894         if (!str) return group_list;
895
896         Xstrdup_a(work, str, return group_list);
897
898         workp = work;
899
900         while (workp && *workp) {
901                 gchar *p, *next;
902
903                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
904                         *p = '\0';
905                         next = p + 1;
906                 } else
907                         next = NULL;
908
909                 g_strstrip(workp);
910                 if (*workp)
911                         group_list = g_slist_append(group_list,
912                                                     g_strdup(workp));
913
914                 workp = next;
915         }
916
917         return group_list;
918 }
919
920 GList *add_history(GList *list, const gchar *str)
921 {
922         GList *old;
923         gchar *oldstr;
924
925         cm_return_val_if_fail(str != NULL, list);
926
927         old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
928         if (old) {
929                 oldstr = old->data;
930                 list = g_list_remove(list, old->data);
931                 g_free(oldstr);
932         } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
933                 GList *last;
934
935                 last = g_list_last(list);
936                 if (last) {
937                         oldstr = last->data;
938                         list = g_list_remove(list, last->data);
939                         g_free(oldstr);
940                 }
941         }
942
943         list = g_list_prepend(list, g_strdup(str));
944
945         return list;
946 }
947
948 void remove_return(gchar *str)
949 {
950         register gchar *p = str;
951
952         while (*p) {
953                 if (*p == '\n' || *p == '\r')
954                         memmove(p, p + 1, strlen(p));
955                 else
956                         p++;
957         }
958 }
959
960 void remove_space(gchar *str)
961 {
962         register gchar *p = str;
963         register gint spc;
964
965         while (*p) {
966                 spc = 0;
967                 while (g_ascii_isspace(*(p + spc)))
968                         spc++;
969                 if (spc)
970                         memmove(p, p + spc, strlen(p + spc) + 1);
971                 else
972                         p++;
973         }
974 }
975
976 void unfold_line(gchar *str)
977 {
978         register gchar *ch;
979         register gunichar c;
980         register gint len;
981
982         ch = str; /* iterator for source string */
983
984         while (*ch != 0) {
985                 c = g_utf8_get_char_validated(ch, -1);
986
987                 if (c == (gunichar)-1 || c == (gunichar)-2) {
988                         /* non-unicode byte, move past it */
989                         ch++;
990                         continue;
991                 }
992
993                 len = g_unichar_to_utf8(c, NULL);
994
995                 if (!g_unichar_isdefined(c) || !g_unichar_isprint(c) ||
996                                 g_unichar_isspace(c)) {
997                         /* replace anything bad or whitespacey with a single space */
998                         *ch = ' ';
999                         ch++;
1000                         if (len > 1) {
1001                                 /* move rest of the string forwards, since we just replaced
1002                                  * a multi-byte sequence with one byte */
1003                                 memmove(ch, ch + len-1, strlen(ch + len-1) + 1);
1004                         }
1005                 } else {
1006                         /* A valid unicode character, copy it. */
1007                         ch += len;
1008                 }
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 (str == NULL || 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 (str == NULL || quote_chars == NULL)
1164                 return NULL;
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_with_plus(file, escaped_utf8uri, FALSE);
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                 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1590                         int i = 0;
1591                         gchar *tmp = decode_uri_gdup(value);
1592                         for (; forbidden_uris[i]; i++) {
1593                                 if (strstr(tmp, forbidden_uris[i])) {
1594                                         g_print("Refusing to attach '%s', potential private data leak\n",
1595                                                         tmp);
1596                                         g_free(tmp);
1597                                         break;
1598                                 }
1599                         }
1600                         if (tmp) {
1601                                 /* attach is correct */
1602                                 num_attach++;
1603                                 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1604                                 my_att[num_attach-1] = tmp;
1605                                 my_att[num_attach] = NULL;
1606                         }
1607                 } else if (inreplyto && !*inreplyto &&
1608                            !g_ascii_strcasecmp(field, "in-reply-to")) {
1609                         *inreplyto = decode_uri_gdup(value);
1610                 }
1611         }
1612
1613         if (attach)
1614                 *attach = my_att;
1615         return 0;
1616 }
1617
1618
1619 #ifdef G_OS_WIN32
1620 #include <windows.h>
1621 #ifndef CSIDL_APPDATA
1622 #define CSIDL_APPDATA 0x001a
1623 #endif
1624 #ifndef CSIDL_LOCAL_APPDATA
1625 #define CSIDL_LOCAL_APPDATA 0x001c
1626 #endif
1627 #ifndef CSIDL_FLAG_CREATE
1628 #define CSIDL_FLAG_CREATE 0x8000
1629 #endif
1630 #define DIM(v)               (sizeof(v)/sizeof((v)[0]))
1631
1632 #define RTLD_LAZY 0
1633 const char *
1634 w32_strerror (int w32_errno)
1635 {
1636   static char strerr[256];
1637   int ec = (int)GetLastError ();
1638
1639   if (w32_errno == 0)
1640     w32_errno = ec;
1641   FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1642                  MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1643                  strerr, DIM (strerr)-1, NULL);
1644   return strerr;
1645 }
1646
1647 static __inline__ void *
1648 dlopen (const char * name, int flag)
1649 {
1650   void * hd = LoadLibrary (name);
1651   return hd;
1652 }
1653
1654 static __inline__ void *
1655 dlsym (void * hd, const char * sym)
1656 {
1657   if (hd && sym)
1658     {
1659       void * fnc = GetProcAddress (hd, sym);
1660       if (!fnc)
1661         return NULL;
1662       return fnc;
1663     }
1664   return NULL;
1665 }
1666
1667
1668 static __inline__ const char *
1669 dlerror (void)
1670 {
1671   return w32_strerror (0);
1672 }
1673
1674
1675 static __inline__ int
1676 dlclose (void * hd)
1677 {
1678   if (hd)
1679     {
1680       FreeLibrary (hd);
1681       return 0;
1682     }
1683   return -1;
1684 }
1685
1686 static HRESULT
1687 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1688 {
1689   static int initialized;
1690   static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1691
1692   if (!initialized)
1693     {
1694       static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1695       void *handle;
1696       int i;
1697
1698       initialized = 1;
1699
1700       for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1701         {
1702           handle = dlopen (dllnames[i], RTLD_LAZY);
1703           if (handle)
1704             {
1705               func = dlsym (handle, "SHGetFolderPathW");
1706               if (!func)
1707                 {
1708                   dlclose (handle);
1709                   handle = NULL;
1710                 }
1711             }
1712         }
1713     }
1714
1715   if (func)
1716     return func (a,b,c,d,e);
1717   else
1718     return -1;
1719 }
1720
1721 /* Returns a static string with the directroy from which the module
1722    has been loaded.  Returns an empty string on error. */
1723 static char *w32_get_module_dir(void)
1724 {
1725         static char *moddir;
1726
1727         if (!moddir) {
1728                 char name[MAX_PATH+10];
1729                 char *p;
1730
1731                 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1732                         *name = 0;
1733                 else {
1734                         p = strrchr (name, '\\');
1735                         if (p)
1736                                 *p = 0;
1737                         else
1738                                 *name = 0;
1739                 }
1740                 moddir = g_strdup (name);
1741         }
1742         return moddir;
1743 }
1744 #endif /* G_OS_WIN32 */
1745
1746 /* Return a static string with the locale dir. */
1747 const gchar *get_locale_dir(void)
1748 {
1749         static gchar *loc_dir;
1750
1751 #ifdef G_OS_WIN32
1752         if (!loc_dir)
1753                 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1754                                       "\\share\\locale", NULL);
1755 #endif
1756         if (!loc_dir)
1757                 loc_dir = LOCALEDIR;
1758         
1759         return loc_dir;
1760 }
1761
1762
1763 const gchar *get_home_dir(void)
1764 {
1765 #ifdef G_OS_WIN32
1766         static char home_dir_utf16[MAX_PATH] = "";
1767         static gchar *home_dir_utf8 = NULL;
1768         if (home_dir_utf16[0] == '\0') {
1769                 if (w32_shgetfolderpath
1770                             (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1771                              NULL, 0, home_dir_utf16) < 0)
1772                                 strcpy (home_dir_utf16, "C:\\Claws Mail");
1773                 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar2 *)home_dir_utf16, -1, NULL, NULL, NULL);
1774         }
1775         return home_dir_utf8;
1776 #else
1777         static const gchar *homeenv = NULL;
1778
1779         if (homeenv)
1780                 return homeenv;
1781
1782         if (!homeenv && g_getenv("HOME") != NULL)
1783                 homeenv = g_strdup(g_getenv("HOME"));
1784         if (!homeenv)
1785                 homeenv = g_get_home_dir();
1786
1787         return homeenv;
1788 #endif
1789 }
1790
1791 static gchar *claws_rc_dir = NULL;
1792 static gboolean rc_dir_alt = FALSE;
1793 const gchar *get_rc_dir(void)
1794 {
1795
1796         if (!claws_rc_dir) {
1797                 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1798                                      RC_DIR, NULL);
1799                 debug_print("using default rc_dir %s\n", claws_rc_dir);
1800         }
1801         return claws_rc_dir;
1802 }
1803
1804 void set_rc_dir(const gchar *dir)
1805 {
1806         gchar *canonical_dir;
1807         if (claws_rc_dir != NULL) {
1808                 g_print("Error: rc_dir already set\n");
1809         } else {
1810                 int err = cm_canonicalize_filename(dir, &canonical_dir);
1811                 int len;
1812
1813                 if (err) {
1814                         g_print("Error looking for %s: %d(%s)\n",
1815                                 dir, -err, g_strerror(-err));
1816                         exit(0);
1817                 }
1818                 rc_dir_alt = TRUE;
1819
1820                 claws_rc_dir = canonical_dir;
1821                 
1822                 len = strlen(claws_rc_dir);
1823                 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1824                         claws_rc_dir[len - 1] = '\0';
1825                 
1826                 debug_print("set rc_dir to %s\n", claws_rc_dir);
1827                 if (!is_dir_exist(claws_rc_dir)) {
1828                         if (make_dir_hier(claws_rc_dir) != 0) {
1829                                 g_print("Error: can't create %s\n",
1830                                 claws_rc_dir);
1831                                 exit(0);
1832                         }
1833                 }
1834         }
1835 }
1836
1837 gboolean rc_dir_is_alt(void) {
1838         return rc_dir_alt;
1839 }
1840
1841 const gchar *get_mail_base_dir(void)
1842 {
1843         return get_home_dir();
1844 }
1845
1846 const gchar *get_news_cache_dir(void)
1847 {
1848         static gchar *news_cache_dir = NULL;
1849         if (!news_cache_dir)
1850                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1851                                              NEWS_CACHE_DIR, NULL);
1852
1853         return news_cache_dir;
1854 }
1855
1856 const gchar *get_imap_cache_dir(void)
1857 {
1858         static gchar *imap_cache_dir = NULL;
1859
1860         if (!imap_cache_dir)
1861                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1862                                              IMAP_CACHE_DIR, NULL);
1863
1864         return imap_cache_dir;
1865 }
1866
1867 const gchar *get_mime_tmp_dir(void)
1868 {
1869         static gchar *mime_tmp_dir = NULL;
1870
1871         if (!mime_tmp_dir)
1872                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1873                                            MIME_TMP_DIR, NULL);
1874
1875         return mime_tmp_dir;
1876 }
1877
1878 const gchar *get_template_dir(void)
1879 {
1880         static gchar *template_dir = NULL;
1881
1882         if (!template_dir)
1883                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1884                                            TEMPLATE_DIR, NULL);
1885
1886         return template_dir;
1887 }
1888
1889 #ifdef G_OS_WIN32
1890 const gchar *w32_get_cert_file(void)
1891 {
1892         const gchar *cert_file = NULL;
1893         if (!cert_file)
1894                 cert_file = g_strconcat(w32_get_module_dir(),
1895                                  "\\share\\claws-mail\\",
1896                                 "ca-certificates.crt",
1897                                 NULL);  
1898         return cert_file;
1899 }
1900 #endif
1901
1902 /* Return the filepath of the claws-mail.desktop file */
1903 const gchar *get_desktop_file(void)
1904 {
1905 #ifdef DESKTOPFILEPATH
1906   return DESKTOPFILEPATH;
1907 #else
1908   return NULL;
1909 #endif
1910 }
1911
1912 /* Return the default directory for Plugins. */
1913 const gchar *get_plugin_dir(void)
1914 {
1915 #ifdef G_OS_WIN32
1916         static gchar *plugin_dir = NULL;
1917
1918         if (!plugin_dir)
1919                 plugin_dir = g_strconcat(w32_get_module_dir(),
1920                                          "\\lib\\claws-mail\\plugins\\",
1921                                          NULL);
1922         return plugin_dir;
1923 #else
1924         if (is_dir_exist(PLUGINDIR))
1925                 return PLUGINDIR;
1926         else {
1927                 static gchar *plugin_dir = NULL;
1928                 if (!plugin_dir)
1929                         plugin_dir = g_strconcat(get_rc_dir(), 
1930                                 G_DIR_SEPARATOR_S, "plugins", 
1931                                 G_DIR_SEPARATOR_S, NULL);
1932                 return plugin_dir;                      
1933         }
1934 #endif
1935 }
1936
1937
1938 #ifdef G_OS_WIN32
1939 /* Return the default directory for Themes. */
1940 const gchar *w32_get_themes_dir(void)
1941 {
1942         static gchar *themes_dir = NULL;
1943
1944         if (!themes_dir)
1945                 themes_dir = g_strconcat(w32_get_module_dir(),
1946                                          "\\share\\claws-mail\\themes",
1947                                          NULL);
1948         return themes_dir;
1949 }
1950 #endif
1951
1952 const gchar *get_tmp_dir(void)
1953 {
1954         static gchar *tmp_dir = NULL;
1955
1956         if (!tmp_dir)
1957                 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1958                                       TMP_DIR, NULL);
1959
1960         return tmp_dir;
1961 }
1962
1963 gchar *get_tmp_file(void)
1964 {
1965         gchar *tmp_file;
1966         static guint32 id = 0;
1967
1968         tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1969                                    get_tmp_dir(), G_DIR_SEPARATOR, id++);
1970
1971         return tmp_file;
1972 }
1973
1974 const gchar *get_domain_name(void)
1975 {
1976 #ifdef G_OS_UNIX
1977         static gchar *domain_name = NULL;
1978         struct addrinfo hints, *res;
1979         char hostname[256];
1980         int s;
1981
1982         if (!domain_name) {
1983                 if (gethostname(hostname, sizeof(hostname)) != 0) {
1984                         perror("gethostname");
1985                         domain_name = "localhost";
1986                 } else {
1987                         memset(&hints, 0, sizeof(struct addrinfo));
1988                         hints.ai_family = AF_UNSPEC;
1989                         hints.ai_socktype = 0;
1990                         hints.ai_flags = AI_CANONNAME;
1991                         hints.ai_protocol = 0;
1992
1993                         s = getaddrinfo(hostname, NULL, &hints, &res);
1994                         if (s != 0) {
1995                                 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
1996                                 domain_name = g_strdup(hostname);
1997                         } else {
1998                                 domain_name = g_strdup(res->ai_canonname);
1999                                 freeaddrinfo(res);
2000                         }
2001                 }
2002                 debug_print("domain name = %s\n", domain_name);
2003         }
2004
2005         return domain_name;
2006 #else
2007         return "localhost";
2008 #endif
2009 }
2010
2011 off_t get_file_size(const gchar *file)
2012 {
2013 #ifdef G_OS_WIN32
2014         GFile *f;
2015         GFileInfo *fi;
2016         GError *error = NULL;
2017         goffset size;
2018
2019         f = g_file_new_for_path(file);
2020         fi = g_file_query_info(f, "standard::size",
2021                         G_FILE_QUERY_INFO_NONE, NULL, &error);
2022         if (error != NULL) {
2023                 debug_print("get_file_size error: %s\n", error->message);
2024                 g_error_free(error);
2025                 g_object_unref(f);
2026                 return -1;
2027         }
2028         size = g_file_info_get_size(fi);
2029         g_object_unref(fi);
2030         g_object_unref(f);
2031         return size;
2032
2033 #else
2034         GStatBuf s;
2035
2036         if (g_stat(file, &s) < 0) {
2037                 FILE_OP_ERROR(file, "stat");
2038                 return -1;
2039         }
2040
2041         return s.st_size;
2042 #endif
2043 }
2044
2045 time_t get_file_mtime(const gchar *file)
2046 {
2047         GStatBuf s;
2048
2049         if (g_stat(file, &s) < 0) {
2050                 FILE_OP_ERROR(file, "stat");
2051                 return -1;
2052         }
2053
2054         return s.st_mtime;
2055 }
2056
2057 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2058 {
2059         GStatBuf s;
2060
2061         if (file == NULL)
2062                 return FALSE;
2063
2064         if (g_stat(file, &s) < 0) {
2065                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2066                 return FALSE;
2067         }
2068
2069         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2070                 return TRUE;
2071
2072         return FALSE;
2073 }
2074
2075
2076 /* Test on whether FILE is a relative file name. This is
2077  * straightforward for Unix but more complex for Windows. */
2078 gboolean is_relative_filename(const gchar *file)
2079 {
2080         if (!file)
2081                 return TRUE;
2082 #ifdef G_OS_WIN32
2083         if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2084                 return FALSE; /* Prefixed with a hostname - this can't
2085                                * be a relative name. */
2086
2087         if ( ((*file >= 'a' && *file <= 'z')
2088               || (*file >= 'A' && *file <= 'Z'))
2089              && file[1] == ':')
2090                 file += 2;  /* Skip drive letter. */
2091
2092         return !(*file == '\\' || *file == '/');
2093 #else
2094         return !(*file == G_DIR_SEPARATOR);
2095 #endif
2096 }
2097
2098
2099 gboolean is_dir_exist(const gchar *dir)
2100 {
2101         if (dir == NULL)
2102                 return FALSE;
2103
2104         return g_file_test(dir, G_FILE_TEST_IS_DIR);
2105 }
2106
2107 gboolean is_file_entry_exist(const gchar *file)
2108 {
2109         if (file == NULL)
2110                 return FALSE;
2111
2112         return g_file_test(file, G_FILE_TEST_EXISTS);
2113 }
2114
2115 gboolean dirent_is_regular_file(struct dirent *d)
2116 {
2117 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2118         if (d->d_type == DT_REG)
2119                 return TRUE;
2120         else if (d->d_type != DT_UNKNOWN)
2121                 return FALSE;
2122 #endif
2123
2124         return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2125 }
2126
2127 gint change_dir(const gchar *dir)
2128 {
2129         gchar *prevdir = NULL;
2130
2131         if (debug_mode)
2132                 prevdir = g_get_current_dir();
2133
2134         if (g_chdir(dir) < 0) {
2135                 FILE_OP_ERROR(dir, "chdir");
2136                 if (debug_mode) g_free(prevdir);
2137                 return -1;
2138         } else if (debug_mode) {
2139                 gchar *cwd;
2140
2141                 cwd = g_get_current_dir();
2142                 if (strcmp(prevdir, cwd) != 0)
2143                         g_print("current dir: %s\n", cwd);
2144                 g_free(cwd);
2145                 g_free(prevdir);
2146         }
2147
2148         return 0;
2149 }
2150
2151 gint make_dir(const gchar *dir)
2152 {
2153         if (g_mkdir(dir, S_IRWXU) < 0) {
2154                 FILE_OP_ERROR(dir, "mkdir");
2155                 return -1;
2156         }
2157         if (g_chmod(dir, S_IRWXU) < 0)
2158                 FILE_OP_ERROR(dir, "chmod");
2159
2160         return 0;
2161 }
2162
2163 gint make_dir_hier(const gchar *dir)
2164 {
2165         gchar *parent_dir;
2166         const gchar *p;
2167
2168         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2169                 parent_dir = g_strndup(dir, p - dir);
2170                 if (*parent_dir != '\0') {
2171                         if (!is_dir_exist(parent_dir)) {
2172                                 if (make_dir(parent_dir) < 0) {
2173                                         g_free(parent_dir);
2174                                         return -1;
2175                                 }
2176                         }
2177                 }
2178                 g_free(parent_dir);
2179         }
2180
2181         if (!is_dir_exist(dir)) {
2182                 if (make_dir(dir) < 0)
2183                         return -1;
2184         }
2185
2186         return 0;
2187 }
2188
2189 gint remove_all_files(const gchar *dir)
2190 {
2191         GDir *dp;
2192         const gchar *file_name;
2193         gchar *tmp;
2194
2195         if ((dp = g_dir_open(dir, 0, NULL)) == NULL) {
2196                 g_warning("failed to open directory: %s", dir);
2197                 return -1;
2198         }
2199
2200         while ((file_name = g_dir_read_name(dp)) != NULL) {
2201                 tmp = g_strconcat(dir, G_DIR_SEPARATOR_S, file_name, NULL);
2202                 if (claws_unlink(tmp) < 0)
2203                         FILE_OP_ERROR(tmp, "unlink");
2204                 g_free(tmp);
2205         }
2206
2207         g_dir_close(dp);
2208
2209         return 0;
2210 }
2211
2212 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2213 {
2214         GDir *dp;
2215         const gchar *dir_name;
2216         gchar *prev_dir;
2217         gint file_no;
2218
2219         if (first == last) {
2220                 /* Skip all the dir reading part. */
2221                 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2222                 if (is_dir_exist(filename)) {
2223                         /* a numbered directory with this name exists,
2224                          * remove the dot-file instead */
2225                         g_free(filename);
2226                         filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2227                 }
2228                 if (claws_unlink(filename) < 0) {
2229                         FILE_OP_ERROR(filename, "unlink");
2230                         g_free(filename);
2231                         return -1;
2232                 }
2233                 g_free(filename);
2234                 return 0;
2235         }
2236
2237         prev_dir = g_get_current_dir();
2238
2239         if (g_chdir(dir) < 0) {
2240                 FILE_OP_ERROR(dir, "chdir");
2241                 g_free(prev_dir);
2242                 return -1;
2243         }
2244
2245         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2246                 g_warning("failed to open directory: %s", dir);
2247                 g_free(prev_dir);
2248                 return -1;
2249         }
2250
2251         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2252                 file_no = to_number(dir_name);
2253                 if (file_no > 0 && first <= file_no && file_no <= last) {
2254                         if (is_dir_exist(dir_name)) {
2255                                 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2256                                 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2257                                         FILE_OP_ERROR(dot_file, "unlink");
2258                                 }
2259                                 g_free(dot_file);
2260                                 continue;
2261                         }
2262                         if (claws_unlink(dir_name) < 0)
2263                                 FILE_OP_ERROR(dir_name, "unlink");
2264                 }
2265         }
2266
2267         g_dir_close(dp);
2268
2269         if (g_chdir(prev_dir) < 0) {
2270                 FILE_OP_ERROR(prev_dir, "chdir");
2271                 g_free(prev_dir);
2272                 return -1;
2273         }
2274
2275         g_free(prev_dir);
2276
2277         return 0;
2278 }
2279
2280 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2281 {
2282         GDir *dp;
2283         const gchar *dir_name;
2284         gchar *prev_dir;
2285         gint file_no;
2286         GHashTable *wanted_files;
2287         GSList *cur;
2288         GError *error = NULL;
2289
2290         if (numberlist == NULL)
2291             return 0;
2292
2293         prev_dir = g_get_current_dir();
2294
2295         if (g_chdir(dir) < 0) {
2296                 FILE_OP_ERROR(dir, "chdir");
2297                 g_free(prev_dir);
2298                 return -1;
2299         }
2300
2301         if ((dp = g_dir_open(".", 0, &error)) == NULL) {
2302                 g_message("Couldn't open current directory: %s (%d).\n",
2303                                 error->message, error->code);
2304                 g_error_free(error);
2305                 g_free(prev_dir);
2306                 return -1;
2307         }
2308
2309         wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2310         for (cur = numberlist; cur != NULL; cur = cur->next) {
2311                 /* numberlist->data is expected to be GINT_TO_POINTER */
2312                 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2313         }
2314
2315         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2316                 file_no = to_number(dir_name);
2317                 if (is_dir_exist(dir_name))
2318                         continue;
2319                 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2320                         debug_print("removing unwanted file %d from %s\n", file_no, dir);
2321                         if (is_dir_exist(dir_name)) {
2322                                 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2323                                 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2324                                         FILE_OP_ERROR(dot_file, "unlink");
2325                                 }
2326                                 g_free(dot_file);
2327                                 continue;
2328                         }
2329                         if (claws_unlink(dir_name) < 0)
2330                                 FILE_OP_ERROR(dir_name, "unlink");
2331                 }
2332         }
2333
2334         g_dir_close(dp);
2335         g_hash_table_destroy(wanted_files);
2336
2337         if (g_chdir(prev_dir) < 0) {
2338                 FILE_OP_ERROR(prev_dir, "chdir");
2339                 g_free(prev_dir);
2340                 return -1;
2341         }
2342
2343         g_free(prev_dir);
2344
2345         return 0;
2346 }
2347
2348 gint remove_all_numbered_files(const gchar *dir)
2349 {
2350         return remove_numbered_files(dir, 0, UINT_MAX);
2351 }
2352
2353 gint remove_dir_recursive(const gchar *dir)
2354 {
2355         GStatBuf s;
2356         GDir *dp;
2357         const gchar *dir_name;
2358         gchar *prev_dir;
2359
2360         if (g_stat(dir, &s) < 0) {
2361                 FILE_OP_ERROR(dir, "stat");
2362                 if (ENOENT == errno) return 0;
2363                 return -(errno);
2364         }
2365
2366         if (!S_ISDIR(s.st_mode)) {
2367                 if (claws_unlink(dir) < 0) {
2368                         FILE_OP_ERROR(dir, "unlink");
2369                         return -(errno);
2370                 }
2371
2372                 return 0;
2373         }
2374
2375         prev_dir = g_get_current_dir();
2376         /* g_print("prev_dir = %s\n", prev_dir); */
2377
2378         if (!path_cmp(prev_dir, dir)) {
2379                 g_free(prev_dir);
2380                 if (g_chdir("..") < 0) {
2381                         FILE_OP_ERROR(dir, "chdir");
2382                         return -(errno);
2383                 }
2384                 prev_dir = g_get_current_dir();
2385         }
2386
2387         if (g_chdir(dir) < 0) {
2388                 FILE_OP_ERROR(dir, "chdir");
2389                 g_free(prev_dir);
2390                 return -(errno);
2391         }
2392
2393         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2394                 g_warning("failed to open directory: %s", dir);
2395                 g_chdir(prev_dir);
2396                 g_free(prev_dir);
2397                 return -(errno);
2398         }
2399
2400         /* remove all files in the directory */
2401         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2402                 /* g_print("removing %s\n", dir_name); */
2403
2404                 if (is_dir_exist(dir_name)) {
2405                         gint ret;
2406
2407                         if ((ret = remove_dir_recursive(dir_name)) < 0) {
2408                                 g_warning("can't remove directory: %s", dir_name);
2409                                 return ret;
2410                         }
2411                 } else {
2412                         if (claws_unlink(dir_name) < 0)
2413                                 FILE_OP_ERROR(dir_name, "unlink");
2414                 }
2415         }
2416
2417         g_dir_close(dp);
2418
2419         if (g_chdir(prev_dir) < 0) {
2420                 FILE_OP_ERROR(prev_dir, "chdir");
2421                 g_free(prev_dir);
2422                 return -(errno);
2423         }
2424
2425         g_free(prev_dir);
2426
2427         if (g_rmdir(dir) < 0) {
2428                 FILE_OP_ERROR(dir, "rmdir");
2429                 return -(errno);
2430         }
2431
2432         return 0;
2433 }
2434
2435 gint rename_force(const gchar *oldpath, const gchar *newpath)
2436 {
2437 #ifndef G_OS_UNIX
2438         if (!is_file_entry_exist(oldpath)) {
2439                 errno = ENOENT;
2440                 return -1;
2441         }
2442         if (is_file_exist(newpath)) {
2443                 if (claws_unlink(newpath) < 0)
2444                         FILE_OP_ERROR(newpath, "unlink");
2445         }
2446 #endif
2447         return g_rename(oldpath, newpath);
2448 }
2449
2450 /*
2451  * Append src file body to the tail of dest file.
2452  * Now keep_backup has no effects.
2453  */
2454 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2455 {
2456         FILE *src_fp, *dest_fp;
2457         gint n_read;
2458         gchar buf[BUFSIZ];
2459
2460         gboolean err = FALSE;
2461
2462         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2463                 FILE_OP_ERROR(src, "g_fopen");
2464                 return -1;
2465         }
2466
2467         if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2468                 FILE_OP_ERROR(dest, "g_fopen");
2469                 fclose(src_fp);
2470                 return -1;
2471         }
2472
2473         if (change_file_mode_rw(dest_fp, dest) < 0) {
2474                 FILE_OP_ERROR(dest, "chmod");
2475                 g_warning("can't change file mode: %s", dest);
2476         }
2477
2478         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2479                 if (n_read < sizeof(buf) && ferror(src_fp))
2480                         break;
2481                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2482                         g_warning("writing to %s failed.", dest);
2483                         fclose(dest_fp);
2484                         fclose(src_fp);
2485                         claws_unlink(dest);
2486                         return -1;
2487                 }
2488         }
2489
2490         if (ferror(src_fp)) {
2491                 FILE_OP_ERROR(src, "fread");
2492                 err = TRUE;
2493         }
2494         fclose(src_fp);
2495         if (fclose(dest_fp) == EOF) {
2496                 FILE_OP_ERROR(dest, "fclose");
2497                 err = TRUE;
2498         }
2499
2500         if (err) {
2501                 claws_unlink(dest);
2502                 return -1;
2503         }
2504
2505         return 0;
2506 }
2507
2508 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2509 {
2510         FILE *src_fp, *dest_fp;
2511         gint n_read;
2512         gchar buf[BUFSIZ];
2513         gchar *dest_bak = NULL;
2514         gboolean err = FALSE;
2515
2516         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2517                 FILE_OP_ERROR(src, "g_fopen");
2518                 return -1;
2519         }
2520         if (is_file_exist(dest)) {
2521                 dest_bak = g_strconcat(dest, ".bak", NULL);
2522                 if (rename_force(dest, dest_bak) < 0) {
2523                         FILE_OP_ERROR(dest, "rename");
2524                         fclose(src_fp);
2525                         g_free(dest_bak);
2526                         return -1;
2527                 }
2528         }
2529
2530         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2531                 FILE_OP_ERROR(dest, "g_fopen");
2532                 fclose(src_fp);
2533                 if (dest_bak) {
2534                         if (rename_force(dest_bak, dest) < 0)
2535                                 FILE_OP_ERROR(dest_bak, "rename");
2536                         g_free(dest_bak);
2537                 }
2538                 return -1;
2539         }
2540
2541         if (change_file_mode_rw(dest_fp, dest) < 0) {
2542                 FILE_OP_ERROR(dest, "chmod");
2543                 g_warning("can't change file mode: %s", dest);
2544         }
2545
2546         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2547                 if (n_read < sizeof(buf) && ferror(src_fp))
2548                         break;
2549                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2550                         g_warning("writing to %s failed.", dest);
2551                         fclose(dest_fp);
2552                         fclose(src_fp);
2553                         claws_unlink(dest);
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
2563         if (ferror(src_fp)) {
2564                 FILE_OP_ERROR(src, "fread");
2565                 err = TRUE;
2566         }
2567         fclose(src_fp);
2568         if (fclose(dest_fp) == EOF) {
2569                 FILE_OP_ERROR(dest, "fclose");
2570                 err = TRUE;
2571         }
2572
2573         if (err) {
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         if (keep_backup == FALSE && dest_bak)
2584                 claws_unlink(dest_bak);
2585
2586         g_free(dest_bak);
2587
2588         return 0;
2589 }
2590
2591 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2592 {
2593         if (overwrite == FALSE && is_file_exist(dest)) {
2594                 g_warning("move_file(): file %s already exists.", dest);
2595                 return -1;
2596         }
2597
2598         if (rename_force(src, dest) == 0) return 0;
2599
2600         if (EXDEV != errno) {
2601                 FILE_OP_ERROR(src, "rename");
2602                 return -1;
2603         }
2604
2605         if (copy_file(src, dest, FALSE) < 0) return -1;
2606
2607         claws_unlink(src);
2608
2609         return 0;
2610 }
2611
2612 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2613 {
2614         gint n_read;
2615         gint bytes_left, to_read;
2616         gchar buf[BUFSIZ];
2617
2618         if (fseek(fp, offset, SEEK_SET) < 0) {
2619                 perror("fseek");
2620                 return -1;
2621         }
2622
2623         bytes_left = length;
2624         to_read = MIN(bytes_left, sizeof(buf));
2625
2626         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2627                 if (n_read < to_read && ferror(fp))
2628                         break;
2629                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2630                         return -1;
2631                 }
2632                 bytes_left -= n_read;
2633                 if (bytes_left == 0)
2634                         break;
2635                 to_read = MIN(bytes_left, sizeof(buf));
2636         }
2637
2638         if (ferror(fp)) {
2639                 perror("fread");
2640                 return -1;
2641         }
2642
2643         return 0;
2644 }
2645
2646 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2647 {
2648         FILE *dest_fp;
2649         gboolean err = FALSE;
2650
2651         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2652                 FILE_OP_ERROR(dest, "g_fopen");
2653                 return -1;
2654         }
2655
2656         if (change_file_mode_rw(dest_fp, dest) < 0) {
2657                 FILE_OP_ERROR(dest, "chmod");
2658                 g_warning("can't change file mode: %s", dest);
2659         }
2660
2661         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2662                 err = TRUE;
2663
2664         if (!err && fclose(dest_fp) == EOF) {
2665                 FILE_OP_ERROR(dest, "fclose");
2666                 err = TRUE;
2667         }
2668
2669         if (err) {
2670                 g_warning("writing to %s failed.", dest);
2671                 claws_unlink(dest);
2672                 return -1;
2673         }
2674
2675         return 0;
2676 }
2677
2678 /* convert line endings into CRLF. If the last line doesn't end with
2679  * linebreak, add it.
2680  */
2681 gchar *canonicalize_str(const gchar *str)
2682 {
2683         const gchar *p;
2684         guint new_len = 0;
2685         gchar *out, *outp;
2686
2687         for (p = str; *p != '\0'; ++p) {
2688                 if (*p != '\r') {
2689                         ++new_len;
2690                         if (*p == '\n')
2691                                 ++new_len;
2692                 }
2693         }
2694         if (p == str || *(p - 1) != '\n')
2695                 new_len += 2;
2696
2697         out = outp = g_malloc(new_len + 1);
2698         for (p = str; *p != '\0'; ++p) {
2699                 if (*p != '\r') {
2700                         if (*p == '\n')
2701                                 *outp++ = '\r';
2702                         *outp++ = *p;
2703                 }
2704         }
2705         if (p == str || *(p - 1) != '\n') {
2706                 *outp++ = '\r';
2707                 *outp++ = '\n';
2708         }
2709         *outp = '\0';
2710
2711         return out;
2712 }
2713
2714 gint canonicalize_file(const gchar *src, const gchar *dest)
2715 {
2716         FILE *src_fp, *dest_fp;
2717         gchar buf[BUFFSIZE];
2718         gint len;
2719         gboolean err = FALSE;
2720         gboolean last_linebreak = FALSE;
2721
2722         if (src == NULL || dest == NULL)
2723                 return -1;
2724
2725         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2726                 FILE_OP_ERROR(src, "g_fopen");
2727                 return -1;
2728         }
2729
2730         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2731                 FILE_OP_ERROR(dest, "g_fopen");
2732                 fclose(src_fp);
2733                 return -1;
2734         }
2735
2736         if (change_file_mode_rw(dest_fp, dest) < 0) {
2737                 FILE_OP_ERROR(dest, "chmod");
2738                 g_warning("can't change file mode: %s", dest);
2739         }
2740
2741         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2742                 gint r = 0;
2743
2744                 len = strlen(buf);
2745                 if (len == 0) break;
2746                 last_linebreak = FALSE;
2747
2748                 if (buf[len - 1] != '\n') {
2749                         last_linebreak = TRUE;
2750                         r = fputs(buf, dest_fp);
2751                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2752                         r = fputs(buf, dest_fp);
2753                 } else {
2754                         if (len > 1) {
2755                                 r = fwrite(buf, 1, len - 1, dest_fp);
2756                                 if (r != (len -1))
2757                                         r = EOF;
2758                         }
2759                         if (r != EOF)
2760                                 r = fputs("\r\n", dest_fp);
2761                 }
2762
2763                 if (r == EOF) {
2764                         g_warning("writing to %s failed.", dest);
2765                         fclose(dest_fp);
2766                         fclose(src_fp);
2767                         claws_unlink(dest);
2768                         return -1;
2769                 }
2770         }
2771
2772         if (last_linebreak == TRUE) {
2773                 if (fputs("\r\n", dest_fp) == EOF)
2774                         err = TRUE;
2775         }
2776
2777         if (ferror(src_fp)) {
2778                 FILE_OP_ERROR(src, "fgets");
2779                 err = TRUE;
2780         }
2781         fclose(src_fp);
2782         if (fclose(dest_fp) == EOF) {
2783                 FILE_OP_ERROR(dest, "fclose");
2784                 err = TRUE;
2785         }
2786
2787         if (err) {
2788                 claws_unlink(dest);
2789                 return -1;
2790         }
2791
2792         return 0;
2793 }
2794
2795 gint canonicalize_file_replace(const gchar *file)
2796 {
2797         gchar *tmp_file;
2798
2799         tmp_file = get_tmp_file();
2800
2801         if (canonicalize_file(file, tmp_file) < 0) {
2802                 g_free(tmp_file);
2803                 return -1;
2804         }
2805
2806         if (move_file(tmp_file, file, TRUE) < 0) {
2807                 g_warning("can't replace file: %s", file);
2808                 claws_unlink(tmp_file);
2809                 g_free(tmp_file);
2810                 return -1;
2811         }
2812
2813         g_free(tmp_file);
2814         return 0;
2815 }
2816
2817 gchar *normalize_newlines(const gchar *str)
2818 {
2819         const gchar *p;
2820         gchar *out, *outp;
2821
2822         out = outp = g_malloc(strlen(str) + 1);
2823         for (p = str; *p != '\0'; ++p) {
2824                 if (*p == '\r') {
2825                         if (*(p + 1) != '\n')
2826                                 *outp++ = '\n';
2827                 } else
2828                         *outp++ = *p;
2829         }
2830
2831         *outp = '\0';
2832
2833         return out;
2834 }
2835
2836 gchar *get_outgoing_rfc2822_str(FILE *fp)
2837 {
2838         gchar buf[BUFFSIZE];
2839         GString *str;
2840         gchar *ret;
2841
2842         str = g_string_new(NULL);
2843
2844         /* output header part */
2845         while (fgets(buf, sizeof(buf), fp) != NULL) {
2846                 strretchomp(buf);
2847                 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2848                         gint next;
2849
2850                         for (;;) {
2851                                 next = fgetc(fp);
2852                                 if (next == EOF)
2853                                         break;
2854                                 else if (next != ' ' && next != '\t') {
2855                                         ungetc(next, fp);
2856                                         break;
2857                                 }
2858                                 if (fgets(buf, sizeof(buf), fp) == NULL)
2859                                         break;
2860                         }
2861                 } else {
2862                         g_string_append(str, buf);
2863                         g_string_append(str, "\r\n");
2864                         if (buf[0] == '\0')
2865                                 break;
2866                 }
2867         }
2868
2869         /* output body part */
2870         while (fgets(buf, sizeof(buf), fp) != NULL) {
2871                 strretchomp(buf);
2872                 if (buf[0] == '.')
2873                         g_string_append_c(str, '.');
2874                 g_string_append(str, buf);
2875                 g_string_append(str, "\r\n");
2876         }
2877
2878         ret = str->str;
2879         g_string_free(str, FALSE);
2880
2881         return ret;
2882 }
2883
2884 /*
2885  * Create a new boundary in a way that it is very unlikely that this
2886  * will occur in the following text.  It would be easy to ensure
2887  * uniqueness if everything is either quoted-printable or base64
2888  * encoded (note that conversion is allowed), but because MIME bodies
2889  * may be nested, it may happen that the same boundary has already
2890  * been used.
2891  *
2892  *   boundary := 0*69<bchars> bcharsnospace
2893  *   bchars := bcharsnospace / " "
2894  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2895  *                  "+" / "_" / "," / "-" / "." /
2896  *                  "/" / ":" / "=" / "?"
2897  *
2898  * some special characters removed because of buggy MTAs
2899  */
2900
2901 gchar *generate_mime_boundary(const gchar *prefix)
2902 {
2903         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2904                              "abcdefghijklmnopqrstuvwxyz"
2905                              "1234567890+_./=";
2906         gchar buf_uniq[24];
2907         gint i;
2908
2909         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2910                 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2911         buf_uniq[i] = '\0';
2912
2913         return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2914                                buf_uniq);
2915 }
2916
2917 gint change_file_mode_rw(FILE *fp, const gchar *file)
2918 {
2919 #if HAVE_FCHMOD
2920         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2921 #else
2922         return g_chmod(file, S_IRUSR|S_IWUSR);
2923 #endif
2924 }
2925
2926 FILE *my_tmpfile(void)
2927 {
2928         const gchar suffix[] = ".XXXXXX";
2929         const gchar *tmpdir;
2930         guint tmplen;
2931         const gchar *progname;
2932         guint proglen;
2933         gchar *fname;
2934         gint fd;
2935         FILE *fp;
2936 #ifndef G_OS_WIN32
2937         gchar buf[2]="\0";
2938 #endif
2939
2940         tmpdir = get_tmp_dir();
2941         tmplen = strlen(tmpdir);
2942         progname = g_get_prgname();
2943         if (progname == NULL)
2944                 progname = "claws-mail";
2945         proglen = strlen(progname);
2946         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2947                 return tmpfile());
2948
2949         memcpy(fname, tmpdir, tmplen);
2950         fname[tmplen] = G_DIR_SEPARATOR;
2951         memcpy(fname + tmplen + 1, progname, proglen);
2952         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2953
2954         fd = g_mkstemp(fname);
2955         if (fd < 0)
2956                 return tmpfile();
2957
2958 #ifndef G_OS_WIN32
2959         claws_unlink(fname);
2960         
2961         /* verify that we can write in the file after unlinking */
2962         if (write(fd, buf, 1) < 0) {
2963                 close(fd);
2964                 return tmpfile();
2965         }
2966         
2967 #endif
2968
2969         fp = fdopen(fd, "w+b");
2970         if (!fp)
2971                 close(fd);
2972         else {
2973                 rewind(fp);
2974                 return fp;
2975         }
2976
2977         return tmpfile();
2978 }
2979
2980 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
2981 {
2982         int fd;
2983         *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
2984         fd = g_mkstemp(*filename);
2985         if (fd < 0)
2986                 return NULL;
2987         return fdopen(fd, "w+");
2988 }
2989
2990 FILE *str_open_as_stream(const gchar *str)
2991 {
2992         FILE *fp;
2993         size_t len;
2994
2995         cm_return_val_if_fail(str != NULL, NULL);
2996
2997         fp = my_tmpfile();
2998         if (!fp) {
2999                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3000                 return NULL;
3001         }
3002
3003         len = strlen(str);
3004         if (len == 0) return fp;
3005
3006         if (fwrite(str, 1, len, fp) != len) {
3007                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3008                 fclose(fp);
3009                 return NULL;
3010         }
3011
3012         rewind(fp);
3013         return fp;
3014 }
3015
3016 gint str_write_to_file(const gchar *str, const gchar *file)
3017 {
3018         FILE *fp;
3019         size_t len;
3020
3021         cm_return_val_if_fail(str != NULL, -1);
3022         cm_return_val_if_fail(file != NULL, -1);
3023
3024         if ((fp = g_fopen(file, "wb")) == NULL) {
3025                 FILE_OP_ERROR(file, "g_fopen");
3026                 return -1;
3027         }
3028
3029         len = strlen(str);
3030         if (len == 0) {
3031                 fclose(fp);
3032                 return 0;
3033         }
3034
3035         if (fwrite(str, 1, len, fp) != len) {
3036                 FILE_OP_ERROR(file, "fwrite");
3037                 fclose(fp);
3038                 claws_unlink(file);
3039                 return -1;
3040         }
3041
3042         if (fclose(fp) == EOF) {
3043                 FILE_OP_ERROR(file, "fclose");
3044                 claws_unlink(file);
3045                 return -1;
3046         }
3047
3048         return 0;
3049 }
3050
3051 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3052 {
3053         GByteArray *array;
3054         guchar buf[BUFSIZ];
3055         gint n_read;
3056         gchar *str;
3057
3058         cm_return_val_if_fail(fp != NULL, NULL);
3059
3060         array = g_byte_array_new();
3061
3062         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3063                 if (n_read < sizeof(buf) && ferror(fp))
3064                         break;
3065                 g_byte_array_append(array, buf, n_read);
3066         }
3067
3068         if (ferror(fp)) {
3069                 FILE_OP_ERROR("file stream", "fread");
3070                 g_byte_array_free(array, TRUE);
3071                 return NULL;
3072         }
3073
3074         buf[0] = '\0';
3075         g_byte_array_append(array, buf, 1);
3076         str = (gchar *)array->data;
3077         g_byte_array_free(array, FALSE);
3078
3079         if (recode && !g_utf8_validate(str, -1, NULL)) {
3080                 const gchar *src_codeset, *dest_codeset;
3081                 gchar *tmp = NULL;
3082                 src_codeset = conv_get_locale_charset_str();
3083                 dest_codeset = CS_UTF_8;
3084                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3085                 g_free(str);
3086                 str = tmp;
3087         }
3088
3089         return str;
3090 }
3091
3092 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3093 {
3094         FILE *fp;
3095         gchar *str;
3096         GStatBuf s;
3097 #ifndef G_OS_WIN32
3098         gint fd, err;
3099         struct timeval timeout = {1, 0};
3100         fd_set fds;
3101         int fflags = 0;
3102 #endif
3103
3104         cm_return_val_if_fail(file != NULL, NULL);
3105
3106         if (g_stat(file, &s) != 0) {
3107                 FILE_OP_ERROR(file, "stat");
3108                 return NULL;
3109         }
3110         if (S_ISDIR(s.st_mode)) {
3111                 g_warning("%s: is a directory", file);
3112                 return NULL;
3113         }
3114
3115 #ifdef G_OS_WIN32
3116         fp = g_fopen (file, "rb");
3117         if (fp == NULL) {
3118                 FILE_OP_ERROR(file, "open");
3119                 return NULL;
3120         }
3121 #else     
3122         /* test whether the file is readable without blocking */
3123         fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3124         if (fd == -1) {
3125                 FILE_OP_ERROR(file, "open");
3126                 return NULL;
3127         }
3128
3129         FD_ZERO(&fds);
3130         FD_SET(fd, &fds);
3131
3132         /* allow for one second */
3133         err = select(fd+1, &fds, NULL, NULL, &timeout);
3134         if (err <= 0 || !FD_ISSET(fd, &fds)) {
3135                 if (err < 0) {
3136                         FILE_OP_ERROR(file, "select");
3137                 } else {
3138                         g_warning("%s: doesn't seem readable", file);
3139                 }
3140                 close(fd);
3141                 return NULL;
3142         }
3143         
3144         /* Now clear O_NONBLOCK */
3145         if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3146                 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3147                 close(fd);
3148                 return NULL;
3149         }
3150         if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3151                 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3152                 close(fd);
3153                 return NULL;
3154         }
3155         
3156         /* get the FILE pointer */
3157         fp = fdopen(fd, "rb");
3158
3159         if (fp == NULL) {
3160                 FILE_OP_ERROR(file, "fdopen");
3161                 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3162                 return NULL;
3163         }
3164 #endif
3165
3166         str = file_read_stream_to_str_full(fp, recode);
3167
3168         fclose(fp);
3169
3170         return str;
3171 }
3172
3173 gchar *file_read_to_str(const gchar *file)
3174 {
3175         return file_read_to_str_full(file, TRUE);
3176 }
3177 gchar *file_read_stream_to_str(FILE *fp)
3178 {
3179         return file_read_stream_to_str_full(fp, TRUE);
3180 }
3181
3182 gchar *file_read_to_str_no_recode(const gchar *file)
3183 {
3184         return file_read_to_str_full(file, FALSE);
3185 }
3186 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3187 {
3188         return file_read_stream_to_str_full(fp, FALSE);
3189 }
3190
3191 char *fgets_crlf(char *buf, int size, FILE *stream)
3192 {
3193         gboolean is_cr = FALSE;
3194         gboolean last_was_cr = FALSE;
3195         int c = 0;
3196         char *cs;
3197
3198         cs = buf;
3199         while (--size > 0 && (c = getc(stream)) != EOF)
3200         {
3201                 *cs++ = c;
3202                 is_cr = (c == '\r');
3203                 if (c == '\n') {
3204                         break;
3205                 }
3206                 if (last_was_cr) {
3207                         *(--cs) = '\n';
3208                         cs++;
3209                         ungetc(c, stream);
3210                         break;
3211                 }
3212                 last_was_cr = is_cr;
3213         }
3214         if (c == EOF && cs == buf)
3215                 return NULL;
3216
3217         *cs = '\0';
3218
3219         return buf;     
3220 }
3221
3222 static gint execute_async(gchar *const argv[], const gchar *working_directory)
3223 {
3224         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3225
3226         if (g_spawn_async(working_directory, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3227                           NULL, NULL, NULL, FALSE) == FALSE) {
3228                 g_warning("couldn't execute command: %s", argv[0]);
3229                 return -1;
3230         }
3231
3232         return 0;
3233 }
3234
3235 static gint execute_sync(gchar *const argv[], const gchar *working_directory)
3236 {
3237         gint status;
3238
3239         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3240
3241 #ifdef G_OS_UNIX
3242         if (g_spawn_sync(working_directory, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3243                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3244                 g_warning("couldn't execute command: %s", argv[0]);
3245                 return -1;
3246         }
3247
3248         if (WIFEXITED(status))
3249                 return WEXITSTATUS(status);
3250         else
3251                 return -1;
3252 #else
3253         if (g_spawn_sync(working_directory, (gchar **)argv, NULL,
3254                                 G_SPAWN_SEARCH_PATH|
3255                                 G_SPAWN_CHILD_INHERITS_STDIN|
3256                                 G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3257                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3258                 g_warning("couldn't execute command: %s", argv[0]);
3259                 return -1;
3260         }
3261
3262         return status;
3263 #endif
3264 }
3265
3266 gint execute_command_line(const gchar *cmdline, gboolean async,
3267                 const gchar *working_directory)
3268 {
3269         gchar **argv;
3270         gint ret;
3271
3272         debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3273
3274         argv = strsplit_with_quote(cmdline, " ", 0);
3275
3276         if (async)
3277                 ret = execute_async(argv, working_directory);
3278         else
3279                 ret = execute_sync(argv, working_directory);
3280
3281         g_strfreev(argv);
3282
3283         return ret;
3284 }
3285
3286 gchar *get_command_output(const gchar *cmdline)
3287 {
3288         gchar *child_stdout;
3289         gint status;
3290
3291         cm_return_val_if_fail(cmdline != NULL, NULL);
3292
3293         debug_print("get_command_output(): executing: %s\n", cmdline);
3294
3295         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3296                                       NULL) == FALSE) {
3297                 g_warning("couldn't execute command: %s", cmdline);
3298                 return NULL;
3299         }
3300
3301         return child_stdout;
3302 }
3303
3304 static gint is_unchanged_uri_char(char c)
3305 {
3306         switch (c) {
3307                 case '(':
3308                 case ')':
3309                         return 0;
3310                 default:
3311                         return 1;
3312         }
3313 }
3314
3315 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3316 {
3317         int i;
3318         int k;
3319
3320         k = 0;
3321         for(i = 0; i < strlen(uri) ; i++) {
3322                 if (is_unchanged_uri_char(uri[i])) {
3323                         if (k + 2 >= bufsize)
3324                                 break;
3325                         encoded_uri[k++] = uri[i];
3326                 }
3327                 else {
3328                         char * hexa = "0123456789ABCDEF";
3329
3330                         if (k + 4 >= bufsize)
3331                                 break;
3332                         encoded_uri[k++] = '%';
3333                         encoded_uri[k++] = hexa[uri[i] / 16];
3334                         encoded_uri[k++] = hexa[uri[i] % 16];
3335                 }
3336         }
3337         encoded_uri[k] = 0;
3338 }
3339
3340 gint open_uri(const gchar *uri, const gchar *cmdline)
3341 {
3342
3343 #ifndef G_OS_WIN32
3344         gchar buf[BUFFSIZE];
3345         gchar *p;
3346         gchar encoded_uri[BUFFSIZE];
3347         cm_return_val_if_fail(uri != NULL, -1);
3348
3349         /* an option to choose whether to use encode_uri or not ? */
3350         encode_uri(encoded_uri, BUFFSIZE, uri);
3351
3352         if (cmdline &&
3353             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3354             !strchr(p + 2, '%'))
3355                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3356         else {
3357                 if (cmdline)
3358                         g_warning("Open URI command-line is invalid "
3359                                   "(there must be only one '%%s'): %s",
3360                                   cmdline);
3361                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3362         }
3363
3364         execute_command_line(buf, TRUE, NULL);
3365 #else
3366         ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3367 #endif
3368         return 0;
3369 }
3370
3371 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3372 {
3373         gchar buf[BUFFSIZE];
3374         gchar *p;
3375
3376         cm_return_val_if_fail(filepath != NULL, -1);
3377
3378         if (cmdline &&
3379             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3380             !strchr(p + 2, '%'))
3381                 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3382         else {
3383                 if (cmdline)
3384                         g_warning("Open Text Editor command-line is invalid "
3385                                   "(there must be only one '%%s'): %s",
3386                                   cmdline);
3387                 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3388         }
3389
3390         execute_command_line(buf, TRUE, NULL);
3391
3392         return 0;
3393 }
3394
3395 time_t remote_tzoffset_sec(const gchar *zone)
3396 {
3397         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3398         gchar zone3[4];
3399         gchar *p;
3400         gchar c;
3401         gint iustz;
3402         gint offset;
3403         time_t remoteoffset;
3404
3405         strncpy(zone3, zone, 3);
3406         zone3[3] = '\0';
3407         remoteoffset = 0;
3408
3409         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3410             (c == '+' || c == '-')) {
3411                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3412                 if (c == '-')
3413                         remoteoffset = -remoteoffset;
3414         } else if (!strncmp(zone, "UT" , 2) ||
3415                    !strncmp(zone, "GMT", 3)) {
3416                 remoteoffset = 0;
3417         } else if (strlen(zone3) == 3) {
3418                 for (p = ustzstr; *p != '\0'; p += 3) {
3419                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3420                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3421                                 remoteoffset = iustz * 3600;
3422                                 break;
3423                         }
3424                 }
3425                 if (*p == '\0')
3426                         return -1;
3427         } else if (strlen(zone3) == 1) {
3428                 switch (zone[0]) {
3429                 case 'Z': remoteoffset =   0; break;
3430                 case 'A': remoteoffset =  -1; break;
3431                 case 'B': remoteoffset =  -2; break;
3432                 case 'C': remoteoffset =  -3; break;
3433                 case 'D': remoteoffset =  -4; break;
3434                 case 'E': remoteoffset =  -5; break;
3435                 case 'F': remoteoffset =  -6; break;
3436                 case 'G': remoteoffset =  -7; break;
3437                 case 'H': remoteoffset =  -8; break;
3438                 case 'I': remoteoffset =  -9; break;
3439                 case 'K': remoteoffset = -10; break; /* J is not used */
3440                 case 'L': remoteoffset = -11; break;
3441                 case 'M': remoteoffset = -12; break;
3442                 case 'N': remoteoffset =   1; break;
3443                 case 'O': remoteoffset =   2; break;
3444                 case 'P': remoteoffset =   3; break;
3445                 case 'Q': remoteoffset =   4; break;
3446                 case 'R': remoteoffset =   5; break;
3447                 case 'S': remoteoffset =   6; break;
3448                 case 'T': remoteoffset =   7; break;
3449                 case 'U': remoteoffset =   8; break;
3450                 case 'V': remoteoffset =   9; break;
3451                 case 'W': remoteoffset =  10; break;
3452                 case 'X': remoteoffset =  11; break;
3453                 case 'Y': remoteoffset =  12; break;
3454                 default:  remoteoffset =   0; break;
3455                 }
3456                 remoteoffset = remoteoffset * 3600;
3457         } else
3458                 return -1;
3459
3460         return remoteoffset;
3461 }
3462
3463 time_t tzoffset_sec(time_t *now)
3464 {
3465         struct tm gmt, *lt;
3466         gint off;
3467         struct tm buf1, buf2;
3468 #ifdef G_OS_WIN32
3469         if (now && *now < 0)
3470                 return 0;
3471 #endif  
3472         gmt = *gmtime_r(now, &buf1);
3473         lt = localtime_r(now, &buf2);
3474
3475         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3476
3477         if (lt->tm_year < gmt.tm_year)
3478                 off -= 24 * 60;
3479         else if (lt->tm_year > gmt.tm_year)
3480                 off += 24 * 60;
3481         else if (lt->tm_yday < gmt.tm_yday)
3482                 off -= 24 * 60;
3483         else if (lt->tm_yday > gmt.tm_yday)
3484                 off += 24 * 60;
3485
3486         if (off >= 24 * 60)             /* should be impossible */
3487                 off = 23 * 60 + 59;     /* if not, insert silly value */
3488         if (off <= -24 * 60)
3489                 off = -(23 * 60 + 59);
3490
3491         return off * 60;
3492 }
3493
3494 /* calculate timezone offset */
3495 gchar *tzoffset(time_t *now)
3496 {
3497         static gchar offset_string[6];
3498         struct tm gmt, *lt;
3499         gint off;
3500         gchar sign = '+';
3501         struct tm buf1, buf2;
3502 #ifdef G_OS_WIN32
3503         if (now && *now < 0)
3504                 return 0;
3505 #endif
3506         gmt = *gmtime_r(now, &buf1);
3507         lt = localtime_r(now, &buf2);
3508
3509         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3510
3511         if (lt->tm_year < gmt.tm_year)
3512                 off -= 24 * 60;
3513         else if (lt->tm_year > gmt.tm_year)
3514                 off += 24 * 60;
3515         else if (lt->tm_yday < gmt.tm_yday)
3516                 off -= 24 * 60;
3517         else if (lt->tm_yday > gmt.tm_yday)
3518                 off += 24 * 60;
3519
3520         if (off < 0) {
3521                 sign = '-';
3522                 off = -off;
3523         }
3524
3525         if (off >= 24 * 60)             /* should be impossible */
3526                 off = 23 * 60 + 59;     /* if not, insert silly value */
3527
3528         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3529
3530         return offset_string;
3531 }
3532
3533 static void _get_rfc822_date(gchar *buf, gint len, gboolean hidetz)
3534 {
3535         struct tm *lt;
3536         time_t t;
3537         gchar day[4], mon[4];
3538         gint dd, hh, mm, ss, yyyy;
3539         struct tm buf1;
3540         gchar buf2[RFC822_DATE_BUFFSIZE];
3541
3542         t = time(NULL);
3543         if (hidetz)
3544                 lt = gmtime_r(&t, &buf1);
3545         else
3546                 lt = localtime_r(&t, &buf1);
3547
3548         if (sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3549                day, mon, &dd, &hh, &mm, &ss, &yyyy) != 7)
3550                 g_warning("failed reading date/time");
3551
3552         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3553                    day, dd, mon, yyyy, hh, mm, ss, (hidetz? "-0000": tzoffset(&t)));
3554 }
3555
3556 void get_rfc822_date(gchar *buf, gint len)
3557 {
3558         _get_rfc822_date(buf, len, FALSE);
3559 }
3560
3561 void get_rfc822_date_hide_tz(gchar *buf, gint len)
3562 {
3563         _get_rfc822_date(buf, len, TRUE);
3564 }
3565
3566 void debug_set_mode(gboolean mode)
3567 {
3568         debug_mode = mode;
3569 }
3570
3571 gboolean debug_get_mode(void)
3572 {
3573         return debug_mode;
3574 }
3575
3576 void debug_print_real(const gchar *format, ...)
3577 {
3578         va_list args;
3579         gchar buf[BUFFSIZE];
3580
3581         if (!debug_mode) return;
3582
3583         va_start(args, format);
3584         g_vsnprintf(buf, sizeof(buf), format, args);
3585         va_end(args);
3586
3587         g_print("%s", buf);
3588 }
3589
3590
3591 const char * debug_srcname(const char *file)
3592 {
3593         const char *s = strrchr (file, '/');
3594         return s? s+1:file;
3595 }
3596
3597
3598 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3599 {
3600         if (subject == NULL)
3601                 subject = "";
3602         else
3603                 subject += subject_get_prefix_length(subject);
3604
3605         return g_hash_table_lookup(subject_table, subject);
3606 }
3607
3608 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3609                           void * data)
3610 {
3611         if (subject == NULL || *subject == 0)
3612                 return;
3613         subject += subject_get_prefix_length(subject);
3614         g_hash_table_insert(subject_table, subject, data);
3615 }
3616
3617 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3618 {
3619         if (subject == NULL)
3620                 return;
3621
3622         subject += subject_get_prefix_length(subject);
3623         g_hash_table_remove(subject_table, subject);
3624 }
3625
3626 static regex_t u_regex;
3627 static gboolean u_init_;
3628
3629 void utils_free_regex(void)
3630 {
3631         if (u_init_) {
3632                 regfree(&u_regex);
3633                 u_init_ = FALSE;
3634         }
3635 }
3636
3637 /*!
3638  *\brief        Check if a string is prefixed with known (combinations)
3639  *              of prefixes. The function assumes that each prefix
3640  *              is terminated by zero or exactly _one_ space.
3641  *
3642  *\param        str String to check for a prefixes
3643  *
3644  *\return       int Number of chars in the prefix that should be skipped
3645  *              for a "clean" subject line. If no prefix was found, 0
3646  *              is returned.
3647  */
3648 int subject_get_prefix_length(const gchar *subject)
3649 {
3650         /*!< Array with allowable reply prefixes regexps. */
3651         static const gchar * const prefixes[] = {
3652                 "Re\\:",                        /* "Re:" */
3653                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3654                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3655                 "Aw\\:",                        /* "Aw:"   (German) */
3656                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3657                 "Res\\:",                       /* "Res:" (Spanish/Brazilian Outlook) */
3658                 "Fw\\:",                        /* "Fw:" Forward */
3659                 "Fwd\\:",                       /* "Fwd:" Forward */
3660                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3661                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3662                 "Rif\\:",                       /* "Rif:" (Italian Outlook) */
3663                 "Sv\\:",                        /* "Sv" (Norwegian) */
3664                 "Vs\\:",                        /* "Vs" (Norwegian) */
3665                 "Ad\\:",                        /* "Ad" (Norwegian) */
3666                 "\347\255\224\345\244\215\\:",  /* "Re" (Chinese, UTF-8) */
3667                 "R\303\251f\\. \\:",            /* "R�f. :" (French Lotus Notes) */
3668                 "Re \\:",                       /* "Re :" (French Yahoo Mail) */
3669                 /* add more */
3670         };
3671         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3672         int n;
3673         regmatch_t pos;
3674
3675         if (!subject) return 0;
3676         if (!*subject) return 0;
3677
3678         if (!u_init_) {
3679                 GString *s = g_string_new("");
3680
3681                 for (n = 0; n < PREFIXES; n++)
3682                         /* Terminate each prefix regexpression by a
3683                          * "\ ?" (zero or ONE space), and OR them */
3684                         g_string_append_printf(s, "(%s\\ ?)%s",
3685                                           prefixes[n],
3686                                           n < PREFIXES - 1 ?
3687                                           "|" : "");
3688
3689                 g_string_prepend(s, "(");
3690                 g_string_append(s, ")+");       /* match at least once */
3691                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3692
3693
3694                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3695                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3696                 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3697                         debug_print("Error compiling regexp %s\n", s->str);
3698                         g_string_free(s, TRUE);
3699                         return 0;
3700                 } else {
3701                         u_init_ = TRUE;
3702                         g_string_free(s, TRUE);
3703                 }
3704         }
3705
3706         if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3707                 return pos.rm_eo;
3708         else
3709                 return 0;
3710 }
3711
3712 static guint g_stricase_hash(gconstpointer gptr)
3713 {
3714         guint hash_result = 0;
3715         const char *str;
3716
3717         for (str = gptr; str && *str; str++) {
3718                 hash_result += toupper(*str);
3719         }
3720
3721         return hash_result;
3722 }
3723
3724 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3725 {
3726         const char *str1 = gptr1;
3727         const char *str2 = gptr2;
3728
3729         return !strcasecmp(str1, str2);
3730 }
3731
3732 gint g_int_compare(gconstpointer a, gconstpointer b)
3733 {
3734         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3735 }
3736
3737 /*
3738    quote_cmd_argument()
3739
3740    return a quoted string safely usable in argument of a command.
3741
3742    code is extracted and adapted from etPan! project -- DINH V. Ho�.
3743 */
3744
3745 gint quote_cmd_argument(gchar * result, guint size,
3746                         const gchar * path)
3747 {
3748         const gchar * p;
3749         gchar * result_p;
3750         guint remaining;
3751
3752         result_p = result;
3753         remaining = size;
3754
3755         for(p = path ; * p != '\0' ; p ++) {
3756
3757                 if (isalnum((guchar)*p) || (* p == '/')) {
3758                         if (remaining > 0) {
3759                                 * result_p = * p;
3760                                 result_p ++;
3761                                 remaining --;
3762                         }
3763                         else {
3764                                 result[size - 1] = '\0';
3765                                 return -1;
3766                         }
3767                 }
3768                 else {
3769                         if (remaining >= 2) {
3770                                 * result_p = '\\';
3771                                 result_p ++;
3772                                 * result_p = * p;
3773                                 result_p ++;
3774                                 remaining -= 2;
3775                         }
3776                         else {
3777                                 result[size - 1] = '\0';
3778                                 return -1;
3779                         }
3780                 }
3781         }
3782         if (remaining > 0) {
3783                 * result_p = '\0';
3784         }
3785         else {
3786                 result[size - 1] = '\0';
3787                 return -1;
3788         }
3789
3790         return 0;
3791 }
3792
3793 typedef struct
3794 {
3795         GNode           *parent;
3796         GNodeMapFunc     func;
3797         gpointer         data;
3798 } GNodeMapData;
3799
3800 static void g_node_map_recursive(GNode *node, gpointer data)
3801 {
3802         GNodeMapData *mapdata = (GNodeMapData *) data;
3803         GNode *newnode;
3804         GNodeMapData newmapdata;
3805         gpointer newdata;
3806
3807         newdata = mapdata->func(node->data, mapdata->data);
3808         if (newdata != NULL) {
3809                 newnode = g_node_new(newdata);
3810                 g_node_append(mapdata->parent, newnode);
3811
3812                 newmapdata.parent = newnode;
3813                 newmapdata.func = mapdata->func;
3814                 newmapdata.data = mapdata->data;
3815
3816                 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3817         }
3818 }
3819
3820 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3821 {
3822         GNode *root;
3823         GNodeMapData mapdata;
3824
3825         cm_return_val_if_fail(node != NULL, NULL);
3826         cm_return_val_if_fail(func != NULL, NULL);
3827
3828         root = g_node_new(func(node->data, data));
3829
3830         mapdata.parent = root;
3831         mapdata.func = func;
3832         mapdata.data = data;
3833
3834         g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3835
3836         return root;
3837 }
3838
3839 #define HEX_TO_INT(val, hex)                    \
3840 {                                               \
3841         gchar c = hex;                          \
3842                                                 \
3843         if ('0' <= c && c <= '9') {             \
3844                 val = c - '0';                  \
3845         } else if ('a' <= c && c <= 'f') {      \
3846                 val = c - 'a' + 10;             \
3847         } else if ('A' <= c && c <= 'F') {      \
3848                 val = c - 'A' + 10;             \
3849         } else {                                \
3850                 val = -1;                       \
3851         }                                       \
3852 }
3853
3854 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3855 {
3856         gint hi, lo;
3857
3858         HEX_TO_INT(hi, c1);
3859         HEX_TO_INT(lo, c2);
3860
3861         if (hi == -1 || lo == -1)
3862                 return FALSE;
3863
3864         *out = (hi << 4) + lo;
3865         return TRUE;
3866 }
3867
3868 #define INT_TO_HEX(hex, val)            \
3869 {                                       \
3870         if ((val) < 10)                 \
3871                 hex = '0' + (val);      \
3872         else                            \
3873                 hex = 'A' + (val) - 10; \
3874 }
3875
3876 void get_hex_str(gchar *out, guchar ch)
3877 {
3878         gchar hex;
3879
3880         INT_TO_HEX(hex, ch >> 4);
3881         *out++ = hex;
3882         INT_TO_HEX(hex, ch & 0x0f);
3883         *out   = hex;
3884 }
3885
3886 #undef REF_DEBUG
3887 #ifndef REF_DEBUG
3888 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3889 #else
3890 #define G_PRINT_REF g_print
3891 #endif
3892
3893 /*!
3894  *\brief        Register ref counted pointer. It is based on GBoxed, so should
3895  *              work with anything that uses the GType system. The semantics
3896  *              are similar to a C++ auto pointer, with the exception that
3897  *              C doesn't have automatic closure (calling destructors) when
3898  *              exiting a block scope.
3899  *              Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3900  *              function directly.
3901  *
3902  *\return       GType A GType type.
3903  */
3904 GType g_auto_pointer_register(void)
3905 {
3906         static GType auto_pointer_type;
3907         if (!auto_pointer_type)
3908                 auto_pointer_type =
3909                         g_boxed_type_register_static
3910                                 ("G_TYPE_AUTO_POINTER",
3911                                  (GBoxedCopyFunc) g_auto_pointer_copy,
3912                                  (GBoxedFreeFunc) g_auto_pointer_free);
3913         return auto_pointer_type;
3914 }
3915
3916 /*!
3917  *\brief        Structure with g_new() allocated pointer guarded by the
3918  *              auto pointer
3919  */
3920 typedef struct AutoPointerRef {
3921         void          (*free) (gpointer);
3922         gpointer        pointer;
3923         glong           cnt;
3924 } AutoPointerRef;
3925
3926 /*!
3927  *\brief        The auto pointer opaque structure that references the
3928  *              pointer guard block.
3929  */
3930 typedef struct AutoPointer {
3931         AutoPointerRef *ref;
3932         gpointer        ptr; /*!< access to protected pointer */
3933 } AutoPointer;
3934
3935 /*!
3936  *\brief        Creates an auto pointer for a g_new()ed pointer. Example:
3937  *
3938  *\code
3939  *
3940  *              ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
3941  *              ... when assigning, copying and freeing storage elements
3942  *
3943  *              gtk_list_store_new(N_S_COLUMNS,
3944  *                                 G_TYPE_AUTO_POINTER,
3945  *                                 -1);
3946  *
3947  *
3948  *              Template *precious_data = g_new0(Template, 1);
3949  *              g_pointer protect = g_auto_pointer_new(precious_data);
3950  *
3951  *              gtk_list_store_set(container, &iter,
3952  *                                 S_DATA, protect,
3953  *                                 -1);
3954  *
3955  *              ... the gtk_list_store has copied the pointer and
3956  *              ... incremented its reference count, we should free
3957  *              ... the auto pointer (in C++ a destructor would do
3958  *              ... this for us when leaving block scope)
3959  *
3960  *              g_auto_pointer_free(protect);
3961  *
3962  *              ... gtk_list_store_set() now manages the data. When
3963  *              ... *explicitly* requesting a pointer from the list
3964  *              ... store, don't forget you get a copy that should be
3965  *              ... freed with g_auto_pointer_free() eventually.
3966  *
3967  *\endcode
3968  *
3969  *\param        pointer Pointer to be guarded.
3970  *
3971  *\return       GAuto * Pointer that should be used in containers with
3972  *              GType support.
3973  */
3974 GAuto *g_auto_pointer_new(gpointer p)
3975 {
3976         AutoPointerRef *ref;
3977         AutoPointer    *ptr;
3978
3979         if (p == NULL)
3980                 return NULL;
3981
3982         ref = g_new0(AutoPointerRef, 1);
3983         ptr = g_new0(AutoPointer, 1);
3984
3985         ref->pointer = p;
3986         ref->free = g_free;
3987         ref->cnt = 1;
3988
3989         ptr->ref = ref;
3990         ptr->ptr = p;
3991
3992 #ifdef REF_DEBUG
3993         G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
3994 #endif
3995         return ptr;
3996 }
3997
3998 /*!
3999  *\brief        Allocate an autopointer using the passed \a free function to
4000  *              free the guarded pointer
4001  */
4002 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4003 {
4004         AutoPointer *aptr;
4005
4006         if (p == NULL)
4007                 return NULL;
4008
4009         aptr = g_auto_pointer_new(p);
4010         aptr->ref->free = free_;
4011         return aptr;
4012 }
4013
4014 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4015 {
4016         if (auto_ptr == NULL)
4017                 return NULL;
4018         return ((AutoPointer *) auto_ptr)->ptr;
4019 }
4020
4021 /*!
4022  *\brief        Copies an auto pointer by. It's mostly not necessary
4023  *              to call this function directly, unless you copy/assign
4024  *              the guarded pointer.
4025  *
4026  *\param        auto_ptr Auto pointer returned by previous call to
4027  *              g_auto_pointer_new_XXX()
4028  *
4029  *\return       gpointer An auto pointer
4030  */
4031 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4032 {
4033         AutoPointer     *ptr;
4034         AutoPointerRef  *ref;
4035         AutoPointer     *newp;
4036
4037         if (auto_ptr == NULL)
4038                 return NULL;
4039
4040         ptr = auto_ptr;
4041         ref = ptr->ref;
4042         newp = g_new0(AutoPointer, 1);
4043
4044         newp->ref = ref;
4045         newp->ptr = ref->pointer;
4046         ++(ref->cnt);
4047
4048 #ifdef REF_DEBUG
4049         G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4050 #endif
4051         return newp;
4052 }
4053
4054 /*!
4055  *\brief        Free an auto pointer
4056  */
4057 void g_auto_pointer_free(GAuto *auto_ptr)
4058 {
4059         AutoPointer     *ptr;
4060         AutoPointerRef  *ref;
4061
4062         if (auto_ptr == NULL)
4063                 return;
4064
4065         ptr = auto_ptr;
4066         ref = ptr->ref;
4067
4068         if (--(ref->cnt) == 0) {
4069 #ifdef REF_DEBUG
4070                 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4071 #endif
4072                 ref->free(ref->pointer);
4073                 g_free(ref);
4074         }
4075 #ifdef REF_DEBUG
4076         else
4077                 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4078 #endif
4079         g_free(ptr);
4080 }
4081
4082 void replace_returns(gchar *str)
4083 {
4084         if (!str)
4085                 return;
4086
4087         while (strstr(str, "\n")) {
4088                 *strstr(str, "\n") = ' ';
4089         }
4090         while (strstr(str, "\r")) {
4091                 *strstr(str, "\r") = ' ';
4092         }
4093 }
4094
4095 /* get_uri_part() - retrieves a URI starting from scanpos.
4096                     Returns TRUE if successful */
4097 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4098                              const gchar **bp, const gchar **ep, gboolean hdr)
4099 {
4100         const gchar *ep_;
4101         gint parenthese_cnt = 0;
4102
4103         cm_return_val_if_fail(start != NULL, FALSE);
4104         cm_return_val_if_fail(scanpos != NULL, FALSE);
4105         cm_return_val_if_fail(bp != NULL, FALSE);
4106         cm_return_val_if_fail(ep != NULL, FALSE);
4107
4108         *bp = scanpos;
4109
4110         /* find end point of URI */
4111         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4112                 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4113                     !IS_ASCII(*(const guchar *)ep_) ||
4114                     strchr("[]{}<>\"", *ep_)) {
4115                         break;
4116                 } else if (strchr("(", *ep_)) {
4117                         parenthese_cnt++;
4118                 } else if (strchr(")", *ep_)) {
4119                         if (parenthese_cnt > 0)
4120                                 parenthese_cnt--;
4121                         else
4122                                 break;
4123                 }
4124         }
4125
4126         /* no punctuation at end of string */
4127
4128         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4129          * should pass some URI type to this function and decide on that whether
4130          * to perform punctuation stripping */
4131
4132 #define IS_REAL_PUNCT(ch)       (g_ascii_ispunct(ch) && !strchr("/?=-_~)", ch))
4133
4134         for (; ep_ - 1 > scanpos + 1 &&
4135                IS_REAL_PUNCT(*(ep_ - 1));
4136              ep_--)
4137                 ;
4138
4139 #undef IS_REAL_PUNCT
4140
4141         *ep = ep_;
4142
4143         return TRUE;
4144 }
4145
4146 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4147 {
4148         while (bp && *bp && g_ascii_isspace(*bp))
4149                 bp++;
4150         return g_strndup(bp, ep - bp);
4151 }
4152
4153 /* valid mail address characters */
4154 #define IS_RFC822_CHAR(ch) \
4155         (IS_ASCII(ch) && \
4156          (ch) > 32   && \
4157          (ch) != 127 && \
4158          !g_ascii_isspace(ch) && \
4159          !strchr("(),;<>\"", (ch)))
4160
4161 /* alphabet and number within 7bit ASCII */
4162 #define IS_ASCII_ALNUM(ch)      (IS_ASCII(ch) && g_ascii_isalnum(ch))
4163 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4164
4165 static GHashTable *create_domain_tab(void)
4166 {
4167         gint n;
4168         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4169
4170         cm_return_val_if_fail(htab, NULL);
4171         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4172                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4173         return htab;
4174 }
4175
4176 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4177 {
4178         const gint MAX_LVL_DOM_NAME_LEN = 6;
4179         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4180         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4181         register gchar *p;
4182
4183         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4184                 return FALSE;
4185
4186         for (p = buf; p < m &&  first < last; *p++ = *first++)
4187                 ;
4188         *p = 0;
4189
4190         return g_hash_table_lookup(tab, buf) != NULL;
4191 }
4192
4193 /* get_email_part() - retrieves an email address. Returns TRUE if successful */
4194 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4195                                const gchar **bp, const gchar **ep, gboolean hdr)
4196 {
4197         /* more complex than the uri part because we need to scan back and forward starting from
4198          * the scan position. */
4199         gboolean result = FALSE;
4200         const gchar *bp_ = NULL;
4201         const gchar *ep_ = NULL;
4202         static GHashTable *dom_tab;
4203         const gchar *last_dot = NULL;
4204         const gchar *prelast_dot = NULL;
4205         const gchar *last_tld_char = NULL;
4206
4207         /* the informative part of the email address (describing the name
4208          * of the email address owner) may contain quoted parts. the
4209          * closure stack stores the last encountered quotes. */
4210         gchar closure_stack[128];
4211         gchar *ptr = closure_stack;
4212
4213         cm_return_val_if_fail(start != NULL, FALSE);
4214         cm_return_val_if_fail(scanpos != NULL, FALSE);
4215         cm_return_val_if_fail(bp != NULL, FALSE);
4216         cm_return_val_if_fail(ep != NULL, FALSE);
4217
4218         if (hdr) {
4219                 const gchar *start_quote = NULL;
4220                 const gchar *end_quote = NULL;
4221 search_again:
4222                 /* go to the real start */
4223                 if (start[0] == ',')
4224                         start++;
4225                 if (start[0] == ';')
4226                         start++;
4227                 while (start[0] == '\n' || start[0] == '\r')
4228                         start++;
4229                 while (start[0] == ' ' || start[0] == '\t')
4230                         start++;
4231
4232                 *bp = start;
4233                 
4234                 /* check if there are quotes (to skip , in them) */
4235                 if (*start == '"') {
4236       &n