aaefe2115681479ce8995077f376ddb48d00a862
[claws.git] / src / common / utils.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 Hiroyuki Yamamoto & The Sylpheed-Claws 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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <errno.h>
32
33 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
34 #  include <wchar.h>
35 #  include <wctype.h>
36 #endif
37 #include <stdlib.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <stdarg.h>
41 #include <sys/types.h>
42 #include <sys/wait.h>
43 #include <dirent.h>
44 #include <time.h>
45 #include <regex.h>
46 #include <sys/utsname.h>
47
48 #include "utils.h"
49 #include "socket.h"
50 #include "../codeconv.h"
51
52 #define BUFFSIZE        8192
53
54 static gboolean debug_mode = FALSE;
55
56 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data);
57
58 void list_free_strings(GList *list)
59 {
60         list = g_list_first(list);
61
62         while (list != NULL) {
63                 g_free(list->data);
64                 list = list->next;
65         }
66 }
67
68 void slist_free_strings(GSList *list)
69 {
70         while (list != NULL) {
71                 g_free(list->data);
72                 list = list->next;
73         }
74 }
75
76 GSList *slist_concat_unique (GSList *first, GSList *second)
77 {
78         GSList *tmp, *ret;
79         if (first == NULL) {
80                 if (second == NULL)
81                         return NULL;
82                 else 
83                         return second;
84         } else if (second == NULL)
85                 return first;
86         ret = first;
87         for (tmp = second; tmp != NULL; tmp = g_slist_next(tmp)) {
88                 if (g_slist_find(ret, tmp->data) == NULL)
89                         ret = g_slist_prepend(ret, tmp->data);
90         }
91         return ret;
92 }
93  
94 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
95 {
96         g_free(key);
97 }
98
99 void hash_free_strings(GHashTable *table)
100 {
101         g_hash_table_foreach(table, hash_free_strings_func, NULL);
102 }
103
104 static void hash_free_value_mem_func(gpointer key, gpointer value,
105                                      gpointer data)
106 {
107         g_free(value);
108 }
109
110 void hash_free_value_mem(GHashTable *table)
111 {
112         g_hash_table_foreach(table, hash_free_value_mem_func, NULL);
113 }
114
115 gint str_case_equal(gconstpointer v, gconstpointer v2)
116 {
117         return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
118 }
119
120 guint str_case_hash(gconstpointer key)
121 {
122         const gchar *p = key;
123         guint h = *p;
124
125         if (h) {
126                 h = tolower(h);
127                 for (p += 1; *p != '\0'; p++)
128                         h = (h << 5) - h + tolower(*p);
129         }
130
131         return h;
132 }
133
134 void ptr_array_free_strings(GPtrArray *array)
135 {
136         gint i;
137         gchar *str;
138
139         g_return_if_fail(array != NULL);
140
141         for (i = 0; i < array->len; i++) {
142                 str = g_ptr_array_index(array, i);
143                 g_free(str);
144         }
145 }
146
147 gboolean str_find(const gchar *haystack, const gchar *needle)
148 {
149         return strstr(haystack, needle) != NULL ? TRUE : FALSE;
150 }
151
152 gboolean str_case_find(const gchar *haystack, const gchar *needle)
153 {
154         return strcasestr(haystack, needle) != NULL ? TRUE : FALSE;
155 }
156
157 gboolean str_find_equal(const gchar *haystack, const gchar *needle)
158 {
159         return strcmp(haystack, needle) == 0;
160 }
161
162 gboolean str_case_find_equal(const gchar *haystack, const gchar *needle)
163 {
164         return strcasecmp(haystack, needle) == 0;
165 }
166
167 gint to_number(const gchar *nstr)
168 {
169         register const guchar *p;
170
171         if (*nstr == '\0') return -1;
172
173         for (p = nstr; *p != '\0'; p++)
174                 if (!isdigit(*p)) return -1;
175
176         return atoi(nstr);
177 }
178
179 /* convert integer into string,
180    nstr must be not lower than 11 characters length */
181 gchar *itos_buf(gchar *nstr, gint n)
182 {
183         g_snprintf(nstr, 11, "%d", n);
184         return nstr;
185 }
186
187 /* convert integer into string */
188 gchar *itos(gint n)
189 {
190         static gchar nstr[11];
191
192         return itos_buf(nstr, n);
193 }
194
195 gchar *to_human_readable(off_t size)
196 {
197         static gchar str[10];
198
199         if (size < 1024)
200                 g_snprintf(str, sizeof(str), _("%dB"), (gint)size);
201         else if (size >> 10 < 1024)
202                 g_snprintf(str, sizeof(str), _("%.1fKB"), (gfloat)size / (1 << 10));
203         else if (size >> 20 < 1024)
204                 g_snprintf(str, sizeof(str), _("%.2fMB"), (gfloat)size / (1 << 20));
205         else
206                 g_snprintf(str, sizeof(str), _("%.2fGB"), (gfloat)size / (1 << 30));
207
208         return str;
209 }
210
211 /* strcmp with NULL-checking */
212 gint strcmp2(const gchar *s1, const gchar *s2)
213 {
214         if (s1 == NULL || s2 == NULL)
215                 return -1;
216         else
217                 return strcmp(s1, s2);
218 }
219 /* strstr with NULL-checking */
220 gchar *strstr2(const gchar *s1, const gchar *s2)
221 {
222         if (s1 == NULL || s2 == NULL)
223                 return NULL;
224         else
225                 return strstr(s1, s2);
226 }
227 /* compare paths */
228 gint path_cmp(const gchar *s1, const gchar *s2)
229 {
230         gint len1, len2;
231
232         if (s1 == NULL || s2 == NULL) return -1;
233         if (*s1 == '\0' || *s2 == '\0') return -1;
234
235         len1 = strlen(s1);
236         len2 = strlen(s2);
237
238         if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
239         if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
240
241         return strncmp(s1, s2, MAX(len1, len2));
242 }
243
244 /* remove trailing return code */
245 gchar *strretchomp(gchar *str)
246 {
247         register gchar *s;
248
249         if (!*str) return str;
250
251         for (s = str + strlen(str) - 1;
252              s >= str && (*s == '\n' || *s == '\r');
253              s--)
254                 *s = '\0';
255
256         return str;
257 }
258
259 /* remove trailing character */
260 gchar *strtailchomp(gchar *str, gchar tail_char)
261 {
262         register gchar *s;
263
264         if (!*str) return str;
265         if (tail_char == '\0') return str;
266
267         for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
268                 *s = '\0';
269
270         return str;
271 }
272
273 /* remove CR (carriage return) */
274 gchar *strcrchomp(gchar *str)
275 {
276         register gchar *s;
277
278         if (!*str) return str;
279
280         s = str + strlen(str) - 1;
281         if (*s == '\n' && s > str && *(s - 1) == '\r') {
282                 *(s - 1) = '\n';
283                 *s = '\0';
284         }
285
286         return str;
287 }
288
289 /* Similar to `strstr' but this function ignores the case of both strings.  */
290 gchar *strcasestr(const gchar *haystack, const gchar *needle)
291 {
292         register size_t haystack_len, needle_len;
293
294         haystack_len = strlen(haystack);
295         needle_len   = strlen(needle);
296
297         if (haystack_len < needle_len || needle_len == 0)
298                 return NULL;
299
300         while (haystack_len >= needle_len) {
301                 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
302                         return (gchar *)haystack;
303                 else {
304                         haystack++;
305                         haystack_len--;
306                 }
307         }
308
309         return NULL;
310 }
311
312 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
313                    gconstpointer needle, size_t needlelen)
314 {
315         const gchar *haystack_ = (const gchar *)haystack;
316         const gchar *needle_ = (const gchar *)needle;
317         const gchar *haystack_cur = (const gchar *)haystack;
318
319         if (needlelen == 1)
320                 return memchr(haystack_, *needle_, haystacklen);
321
322         while ((haystack_cur = memchr(haystack_cur, *needle_, haystacklen))
323                != NULL) {
324                 if (haystacklen - (haystack_cur - haystack_) < needlelen)
325                         break;
326                 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
327                         return (gpointer)haystack_cur;
328                 else
329                         haystack_cur++;
330         }
331
332         return NULL;
333 }
334
335 /* Copy no more than N characters of SRC to DEST, with NULL terminating.  */
336 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
337 {
338         register gchar c;
339         gchar *s = dest;
340
341         do {
342                 if (--n == 0) {
343                         *dest = '\0';
344                         return s;
345                 }
346                 c = *src++;
347                 *dest++ = c;
348         } while (c != '\0');
349
350         /* don't do zero fill */
351         return s;
352 }
353
354 #if !HAVE_ISWALNUM
355 int iswalnum(wint_t wc)
356 {
357         return isalnum((int)wc);
358 }
359 #endif
360
361 #if !HAVE_ISWSPACE
362 int iswspace(wint_t wc)
363 {
364         return isspace((int)wc);
365 }
366 #endif
367
368 #if !HAVE_TOWLOWER
369 wint_t towlower(wint_t wc)
370 {
371         if (wc >= L'A' && wc <= L'Z')
372                 return wc + L'a' - L'A';
373
374         return wc;
375 }
376 #endif
377
378 #if !HAVE_WCSLEN
379 size_t wcslen(const wchar_t *s)
380 {
381         size_t len = 0;
382
383         while (*s != L'\0')
384                 ++len, ++s;
385
386         return len;
387 }
388 #endif
389
390 #if !HAVE_WCSCPY
391 /* Copy SRC to DEST.  */
392 wchar_t *wcscpy(wchar_t *dest, const wchar_t *src)
393 {
394         wint_t c;
395         wchar_t *s = dest;
396
397         do {
398                 c = *src++;
399                 *dest++ = c;
400         } while (c != L'\0');
401
402         return s;
403 }
404 #endif
405
406 #if !HAVE_WCSNCPY
407 /* Copy no more than N wide-characters of SRC to DEST.  */
408 wchar_t *wcsncpy (wchar_t *dest, const wchar_t *src, size_t n)
409 {
410         wint_t c;
411         wchar_t *s = dest;
412
413         do {
414                 c = *src++;
415                 *dest++ = c;
416                 if (--n == 0)
417                         return s;
418         } while (c != L'\0');
419
420         /* zero fill */
421         do
422                 *dest++ = L'\0';
423         while (--n > 0);
424
425         return s;
426 }
427 #endif
428
429 /* Duplicate S, returning an identical malloc'd string. */
430 wchar_t *wcsdup(const wchar_t *s)
431 {
432         wchar_t *new_str;
433
434         if (s) {
435                 new_str = g_new(wchar_t, wcslen(s) + 1);
436                 wcscpy(new_str, s);
437         } else
438                 new_str = NULL;
439
440         return new_str;
441 }
442
443 /* Duplicate no more than N wide-characters of S,
444    returning an identical malloc'd string. */
445 wchar_t *wcsndup(const wchar_t *s, size_t n)
446 {
447         wchar_t *new_str;
448
449         if (s) {
450                 new_str = g_new(wchar_t, n + 1);
451                 wcsncpy(new_str, s, n);
452                 new_str[n] = (wchar_t)0;
453         } else
454                 new_str = NULL;
455
456         return new_str;
457 }
458
459 wchar_t *strdup_mbstowcs(const gchar *s)
460 {
461         wchar_t *new_str;
462
463         if (s) {
464                 new_str = g_new(wchar_t, strlen(s) + 1);
465                 if (mbstowcs(new_str, s, strlen(s) + 1) < 0) {
466                         g_free(new_str);
467                         new_str = NULL;
468                 } else
469                         new_str = g_realloc(new_str,
470                                             sizeof(wchar_t) * (wcslen(new_str) + 1));
471         } else
472                 new_str = NULL;
473
474         return new_str;
475 }
476
477 gchar *strdup_wcstombs(const wchar_t *s)
478 {
479         gchar *new_str;
480         size_t len;
481
482         if (s) {
483                 len = wcslen(s) * MB_CUR_MAX + 1;
484                 new_str = g_new(gchar, len);
485                 if (wcstombs(new_str, s, len) < 0) {
486                         g_free(new_str);
487                         new_str = NULL;
488                 } else
489                         new_str = g_realloc(new_str, strlen(new_str) + 1);
490         } else
491                 new_str = NULL;
492
493         return new_str;
494 }
495
496 /* Compare S1 and S2, ignoring case.  */
497 gint wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n)
498 {
499         wint_t c1;
500         wint_t c2;
501
502         while (n--) {
503                 c1 = towlower(*s1++);
504                 c2 = towlower(*s2++);
505                 if (c1 != c2)
506                         return c1 - c2;
507                 else if (c1 == 0 && c2 == 0)
508                         break;
509         }
510
511         return 0;
512 }
513
514 /* Find the first occurrence of NEEDLE in HAYSTACK, ignoring case.  */
515 wchar_t *wcscasestr(const wchar_t *haystack, const wchar_t *needle)
516 {
517         register size_t haystack_len, needle_len;
518
519         haystack_len = wcslen(haystack);
520         needle_len   = wcslen(needle);
521
522         if (haystack_len < needle_len || needle_len == 0)
523                 return NULL;
524
525         while (haystack_len >= needle_len) {
526                 if (!wcsncasecmp(haystack, needle, needle_len))
527                         return (wchar_t *)haystack;
528                 else {
529                         haystack++;
530                         haystack_len--;
531                 }
532         }
533
534         return NULL;
535 }
536
537 gint get_mbs_len(const gchar *s)
538 {
539         const gchar *p = s;
540         gint mb_len;
541         gint len = 0;
542
543         if (!p)
544                 return -1;
545
546         while (*p != '\0') {
547                 mb_len = g_utf8_skip[*(guchar *)p];
548                 if (mb_len == 0)
549                         break;
550                 else
551                         len++;
552
553                 p += mb_len;
554         }
555
556         return len;
557 }
558
559 /* Examine if next block is non-ASCII string */
560 gboolean is_next_nonascii(const guchar *s)
561 {
562         const guchar *p;
563
564         /* skip head space */
565         for (p = s; *p != '\0' && isspace(*p); p++)
566                 ;
567         for (; *p != '\0' && !isspace(*p); p++) {
568                 if (*p > 127 || *p < 32)
569                         return TRUE;
570         }
571
572         return FALSE;
573 }
574
575 gint get_next_word_len(const guchar *s)
576 {
577         gint len = 0;
578
579         for (; *s != '\0' && !isspace(*s); s++, len++)
580                 ;
581
582         return len;
583 }
584
585 /* compare subjects */
586 gint subject_compare(const gchar *s1, const gchar *s2)
587 {
588         gchar *str1, *str2;
589
590         if (!s1 || !s2) return -1;
591         if (!*s1 || !*s2) return -1;
592
593         Xstrdup_a(str1, s1, return -1);
594         Xstrdup_a(str2, s2, return -1);
595
596         trim_subject_for_compare(str1);
597         trim_subject_for_compare(str2);
598
599         if (!*str1 || !*str2) return -1;
600
601         return strcmp(str1, str2);
602 }
603
604 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
605 {
606         gchar *str1, *str2;
607
608         if (!s1 || !s2) return -1;
609
610         Xstrdup_a(str1, s1, return -1);
611         Xstrdup_a(str2, s2, return -1);
612
613         trim_subject_for_sort(str1);
614         trim_subject_for_sort(str2);
615
616         return g_utf8_collate(str1, str2);
617 }
618
619 void trim_subject_for_compare(gchar *str)
620 {
621         gchar *srcp;
622
623         eliminate_parenthesis(str, '[', ']');
624         eliminate_parenthesis(str, '(', ')');
625         g_strstrip(str);
626
627         srcp = str + subject_get_prefix_length(str);
628         if (srcp != str)
629                 memmove(str, srcp, strlen(srcp) + 1);
630 }
631
632 void trim_subject_for_sort(gchar *str)
633 {
634         gchar *srcp;
635
636         g_strstrip(str);
637
638         srcp = str + subject_get_prefix_length(str);
639         if (srcp != str)        
640                 memmove(str, srcp, strlen(srcp) + 1);
641 }
642
643 void trim_subject(gchar *str)
644 {
645         register guchar *srcp, *destp;
646         gchar op, cl;
647         gint in_brace;
648
649         destp = str + subject_get_prefix_length(str);
650
651         if (*destp == '[') {
652                 op = '[';
653                 cl = ']';
654         } else if (*destp == '(') {
655                 op = '(';
656                 cl = ')';
657         } else
658                 return;
659
660         srcp = destp + 1;
661         in_brace = 1;
662         while (*srcp) {
663                 if (*srcp == op)
664                         in_brace++;
665                 else if (*srcp == cl)
666                         in_brace--;
667                 srcp++;
668                 if (in_brace == 0)
669                         break;
670         }
671         while (isspace(*srcp)) srcp++;
672         memmove(destp, srcp, strlen(srcp) + 1);
673 }
674
675 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
676 {
677         register guchar *srcp, *destp;
678         gint in_brace;
679
680         srcp = destp = str;
681
682         while ((destp = strchr(destp, op))) {
683                 in_brace = 1;
684                 srcp = destp + 1;
685                 while (*srcp) {
686                         if (*srcp == op)
687                                 in_brace++;
688                         else if (*srcp == cl)
689                                 in_brace--;
690                         srcp++;
691                         if (in_brace == 0)
692                                 break;
693                 }
694                 while (isspace(*srcp)) srcp++;
695                 memmove(destp, srcp, strlen(srcp) + 1);
696         }
697 }
698
699 void extract_parenthesis(gchar *str, gchar op, gchar cl)
700 {
701         register gchar *srcp, *destp;
702         gint in_brace;
703
704         srcp = destp = str;
705
706         while ((srcp = strchr(destp, op))) {
707                 if (destp > str)
708                         *destp++ = ' ';
709                 memmove(destp, srcp + 1, strlen(srcp));
710                 in_brace = 1;
711                 while(*destp) {
712                         if (*destp == op)
713                                 in_brace++;
714                         else if (*destp == cl)
715                                 in_brace--;
716
717                         if (in_brace == 0)
718                                 break;
719
720                         destp++;
721                 }
722         }
723         *destp = '\0';
724 }
725
726 void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
727                                          gchar op, gchar cl)
728 {
729         register gchar *srcp, *destp;
730         gint in_brace;
731         gboolean in_quote = FALSE;
732
733         srcp = destp = str;
734
735         while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
736                 if (destp > str)
737                         *destp++ = ' ';
738                 memmove(destp, srcp + 1, strlen(srcp));
739                 in_brace = 1;
740                 while(*destp) {
741                         if (*destp == op && !in_quote)
742                                 in_brace++;
743                         else if (*destp == cl && !in_quote)
744                                 in_brace--;
745                         else if (*destp == quote_chr)
746                                 in_quote ^= TRUE;
747
748                         if (in_brace == 0)
749                                 break;
750
751                         destp++;
752                 }
753         }
754         *destp = '\0';
755 }
756
757 void eliminate_quote(gchar *str, gchar quote_chr)
758 {
759         register guchar *srcp, *destp;
760
761         srcp = destp = str;
762
763         while ((destp = strchr(destp, quote_chr))) {
764                 if ((srcp = strchr(destp + 1, quote_chr))) {
765                         srcp++;
766                         while (isspace(*srcp)) srcp++;
767                         memmove(destp, srcp, strlen(srcp) + 1);
768                 } else {
769                         *destp = '\0';
770                         break;
771                 }
772         }
773 }
774
775 void extract_quote(gchar *str, gchar quote_chr)
776 {
777         register gchar *p;
778
779         if ((str = strchr(str, quote_chr))) {
780                 p = str;
781                 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
782                         memmove(p - 1, p, strlen(p) + 1);
783                         p--;
784                 }
785                 if(p) {
786                         *p = '\0';
787                         memmove(str, str + 1, p - str);
788                 }
789         }
790 }
791
792 void eliminate_address_comment(gchar *str)
793 {
794         register guchar *srcp, *destp;
795         gint in_brace;
796
797         srcp = destp = str;
798
799         while ((destp = strchr(destp, '"'))) {
800                 if ((srcp = strchr(destp + 1, '"'))) {
801                         srcp++;
802                         if (*srcp == '@') {
803                                 destp = srcp + 1;
804                         } else {
805                                 while (isspace(*srcp)) srcp++;
806                                 memmove(destp, srcp, strlen(srcp) + 1);
807                         }
808                 } else {
809                         *destp = '\0';
810                         break;
811                 }
812         }
813
814         srcp = destp = str;
815
816         while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
817                 in_brace = 1;
818                 srcp = destp + 1;
819                 while (*srcp) {
820                         if (*srcp == '(')
821                                 in_brace++;
822                         else if (*srcp == ')')
823                                 in_brace--;
824                         srcp++;
825                         if (in_brace == 0)
826                                 break;
827                 }
828                 while (isspace(*srcp)) srcp++;
829                 memmove(destp, srcp, strlen(srcp) + 1);
830         }
831 }
832
833 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
834 {
835         gboolean in_quote = FALSE;
836
837         while (*str) {
838                 if (*str == c && !in_quote)
839                         return (gchar *)str;
840                 if (*str == quote_chr)
841                         in_quote ^= TRUE;
842                 str++;
843         }
844
845         return NULL;
846 }
847
848 gchar *strrchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
849 {
850         gboolean in_quote = FALSE;
851         const gchar *p;
852
853         p = str + strlen(str) - 1;
854         while (p >= str) {
855                 if (*p == c && !in_quote)
856                         return (gchar *)p;
857                 if (*p == quote_chr)
858                         in_quote ^= TRUE;
859                 p--;
860         }
861
862         return NULL;
863 }
864
865 void extract_address(gchar *str)
866 {
867         eliminate_address_comment(str);
868         if (strchr_with_skip_quote(str, '"', '<'))
869                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
870         g_strstrip(str);
871 }
872
873 void extract_list_id_str(gchar *str)
874 {
875         if (strchr_with_skip_quote(str, '"', '<'))
876                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
877         g_strstrip(str);
878 }
879
880 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
881 {
882         gchar *work;
883         gchar *workp;
884
885         if (!str) return addr_list;
886
887         Xstrdup_a(work, str, return addr_list);
888
889         if (removecomments)
890                 eliminate_address_comment(work);
891         workp = work;
892
893         while (workp && *workp) {
894                 gchar *p, *next;
895
896                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
897                         *p = '\0';
898                         next = p + 1;
899                 } else
900                         next = NULL;
901
902                 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
903                         extract_parenthesis_with_skip_quote
904                                 (workp, '"', '<', '>');
905
906                 g_strstrip(workp);
907                 if (*workp)
908                         addr_list = g_slist_append(addr_list, g_strdup(workp));
909
910                 workp = next;
911         }
912
913         return addr_list;
914 }
915
916 GSList *address_list_append(GSList *addr_list, const gchar *str)
917 {
918         return address_list_append_real(addr_list, str, TRUE);
919 }
920
921 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
922 {
923         return address_list_append_real(addr_list, str, FALSE);
924 }
925
926 GSList *references_list_append(GSList *msgid_list, const gchar *str)
927 {
928         const gchar *strp;
929
930         if (!str) return msgid_list;
931         strp = str;
932
933         while (strp && *strp) {
934                 const gchar *start, *end;
935                 gchar *msgid;
936
937                 if ((start = strchr(strp, '<')) != NULL) {
938                         end = strchr(start + 1, '>');
939                         if (!end) break;
940                 } else
941                         break;
942
943                 msgid = g_strndup(start + 1, end - start - 1);
944                 g_strstrip(msgid);
945                 if (*msgid)
946                         msgid_list = g_slist_append(msgid_list, msgid);
947                 else
948                         g_free(msgid);
949
950                 strp = end + 1;
951         }
952
953         return msgid_list;
954 }
955
956 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
957 {
958         gchar *work;
959         gchar *workp;
960
961         if (!str) return group_list;
962
963         Xstrdup_a(work, str, return group_list);
964
965         workp = work;
966
967         while (workp && *workp) {
968                 gchar *p, *next;
969
970                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
971                         *p = '\0';
972                         next = p + 1;
973                 } else
974                         next = NULL;
975
976                 g_strstrip(workp);
977                 if (*workp)
978                         group_list = g_slist_append(group_list,
979                                                     g_strdup(workp));
980
981                 workp = next;
982         }
983
984         return group_list;
985 }
986
987 GList *add_history(GList *list, const gchar *str)
988 {
989         GList *old;
990
991         g_return_val_if_fail(str != NULL, list);
992
993         old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
994         if (old) {
995                 g_free(old->data);
996                 list = g_list_remove(list, old->data);
997         } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
998                 GList *last;
999
1000                 last = g_list_last(list);
1001                 if (last) {
1002                         g_free(last->data);
1003                         g_list_remove(list, last->data);
1004                 }
1005         }
1006
1007         list = g_list_prepend(list, g_strdup(str));
1008
1009         return list;
1010 }
1011
1012 void remove_return(gchar *str)
1013 {
1014         register gchar *p = str;
1015
1016         while (*p) {
1017                 if (*p == '\n' || *p == '\r')
1018                         memmove(p, p + 1, strlen(p));
1019                 else
1020                         p++;
1021         }
1022 }
1023
1024 void remove_space(gchar *str)
1025 {
1026         register guchar *p = str;
1027         register gint spc;
1028
1029         while (*p) {
1030                 spc = 0;
1031                 while (isspace(*(p + spc)))
1032                         spc++;
1033                 if (spc)
1034                         memmove(p, p + spc, strlen(p + spc) + 1);
1035                 else
1036                         p++;
1037         }
1038 }
1039
1040 void unfold_line(gchar *str)
1041 {
1042         register guchar *p = str;
1043         register gint spc;
1044
1045         while (*p) {
1046                 if (*p == '\n' || *p == '\r') {
1047                         *p++ = ' ';
1048                         spc = 0;
1049                         while (isspace(*(p + spc)))
1050                                 spc++;
1051                         if (spc)
1052                                 memmove(p, p + spc, strlen(p + spc) + 1);
1053                 } else
1054                         p++;
1055         }
1056 }
1057
1058 void subst_char(gchar *str, gchar orig, gchar subst)
1059 {
1060         register gchar *p = str;
1061
1062         while (*p) {
1063                 if (*p == orig)
1064                         *p = subst;
1065                 p++;
1066         }
1067 }
1068
1069 void subst_chars(gchar *str, gchar *orig, gchar subst)
1070 {
1071         register gchar *p = str;
1072
1073         while (*p) {
1074                 if (strchr(orig, *p) != NULL)
1075                         *p = subst;
1076                 p++;
1077         }
1078 }
1079
1080 void subst_for_filename(gchar *str)
1081 {
1082         subst_chars(str, " \t\r\n\"'/\\", '_');
1083 }
1084
1085 void subst_for_shellsafe_filename(gchar *str)
1086 {
1087         subst_for_filename(str);
1088         subst_chars(str, "|&;()<>'!{}[]",'_');
1089 }
1090
1091 gboolean is_header_line(const gchar *str)
1092 {
1093         if (str[0] == ':') return FALSE;
1094
1095         while (*str != '\0' && *str != ' ') {
1096                 if (*str == ':')
1097                         return TRUE;
1098                 str++;
1099         }
1100
1101         return FALSE;
1102 }
1103
1104 gboolean is_ascii_str(const guchar *str)
1105 {
1106         g_return_val_if_fail(str, FALSE);
1107
1108         while (*str != '\0') {
1109                 if (*str != '\t' && *str != ' ' &&
1110                     *str != '\r' && *str != '\n' &&
1111                     (*str < 32 || *str >= 127))
1112                         return FALSE;
1113                 str++;
1114         }
1115
1116         return TRUE;
1117 }
1118
1119 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1120 {
1121         const guchar *first_pos;
1122         const guchar *last_pos;
1123         const guchar *p = str;
1124         gint quote_level = -1;
1125
1126         /* speed up line processing by only searching to the last '>' */
1127         if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1128                 /* skip a line if it contains a '<' before the initial '>' */
1129                 if (memchr(str, '<', first_pos - (const guchar *)str) != NULL)
1130                         return -1;
1131                 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1132         } else
1133                 return -1;
1134
1135         while (p <= last_pos) {
1136                 while (p < last_pos) {
1137                         if (isspace(*p))
1138                                 p++;
1139                         else
1140                                 break;
1141                 }
1142
1143                 if (strchr(quote_chars, *p))
1144                         quote_level++;
1145                 else if (*p != '-' && !isspace(*p) && p <= last_pos) {
1146                         /* any characters are allowed except '-' and space */
1147                         while (*p != '-' 
1148                                && !strchr(quote_chars, *p) 
1149                                && !isspace(*p) 
1150                                && p < last_pos)
1151                                 p++;
1152                         if (strchr(quote_chars, *p))
1153                                 quote_level++;
1154                         else
1155                                 break;
1156                 }
1157
1158                 p++;
1159         }
1160
1161         return quote_level;
1162 }
1163
1164 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars) 
1165 {
1166         gchar * position = NULL;
1167         gchar * tmp_pos = NULL;
1168         int i;
1169
1170         if (quote_chars == NULL)
1171                 return FALSE;
1172         
1173         for (i = 0; i < strlen(quote_chars); i++) {
1174                 tmp_pos = strchr (str,  quote_chars[i]);
1175                 if(position == NULL 
1176                    || (tmp_pos != NULL && position >= tmp_pos) )
1177                         position = tmp_pos;
1178         }
1179         return position; 
1180 }
1181
1182 const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars) 
1183 {
1184         gchar * position = NULL;
1185         gchar * tmp_pos = NULL;
1186         int i;
1187
1188         if (quote_chars == NULL)
1189                 return FALSE;
1190         
1191         for (i = 0; i < strlen(quote_chars); i++) {
1192                 tmp_pos = strrchr (str, quote_chars[i]);
1193                 if(position == NULL 
1194                    || (tmp_pos != NULL && position <= tmp_pos) )
1195                         position = tmp_pos;
1196         }
1197         return position; 
1198 }
1199
1200 gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1201 {
1202         register guint haystack_len, needle_len;
1203         gboolean in_squote = FALSE, in_dquote = FALSE;
1204
1205         haystack_len = strlen(haystack);
1206         needle_len   = strlen(needle);
1207
1208         if (haystack_len < needle_len || needle_len == 0)
1209                 return NULL;
1210
1211         while (haystack_len >= needle_len) {
1212                 if (!in_squote && !in_dquote &&
1213                     !strncmp(haystack, needle, needle_len))
1214                         return (gchar *)haystack;
1215
1216                 /* 'foo"bar"' -> foo"bar"
1217                    "foo'bar'" -> foo'bar' */
1218                 if (*haystack == '\'') {
1219                         if (in_squote)
1220                                 in_squote = FALSE;
1221                         else if (!in_dquote)
1222                                 in_squote = TRUE;
1223                 } else if (*haystack == '\"') {
1224                         if (in_dquote)
1225                                 in_dquote = FALSE;
1226                         else if (!in_squote)
1227                                 in_dquote = TRUE;
1228                 }
1229
1230                 haystack++;
1231                 haystack_len--;
1232         }
1233
1234         return NULL;
1235 }
1236
1237 gchar *strchr_parenthesis_close(const gchar *str, gchar op, gchar cl)
1238 {
1239         const gchar *p;
1240         gchar quote_chr = '"';
1241         gint in_brace;
1242         gboolean in_quote = FALSE;
1243
1244         p = str;
1245
1246         if ((p = strchr_with_skip_quote(p, quote_chr, op))) {
1247                 p++;
1248                 in_brace = 1;
1249                 while (*p) {
1250                         if (*p == op && !in_quote)
1251                                 in_brace++;
1252                         else if (*p == cl && !in_quote)
1253                                 in_brace--;
1254                         else if (*p == quote_chr)
1255                                 in_quote ^= TRUE;
1256
1257                         if (in_brace == 0)
1258                                 return (gchar *)p;
1259
1260                         p++;
1261                 }
1262         }
1263
1264         return NULL;
1265 }
1266
1267 gchar **strsplit_parenthesis(const gchar *str, gchar op, gchar cl,
1268                              gint max_tokens)
1269 {
1270         GSList *string_list = NULL, *slist;
1271         gchar **str_array;
1272         const gchar *s_op, *s_cl;
1273         guint i, n = 1;
1274
1275         g_return_val_if_fail(str != NULL, NULL);
1276
1277         if (max_tokens < 1)
1278                 max_tokens = G_MAXINT;
1279
1280         s_op = strchr_with_skip_quote(str, '"', op);
1281         if (!s_op) return NULL;
1282         str = s_op;
1283         s_cl = strchr_parenthesis_close(str, op, cl);
1284         if (s_cl) {
1285                 do {
1286                         guint len;
1287                         gchar *new_string;
1288
1289                         str++;
1290                         len = s_cl - str;
1291                         new_string = g_new(gchar, len + 1);
1292                         strncpy(new_string, str, len);
1293                         new_string[len] = 0;
1294                         string_list = g_slist_prepend(string_list, new_string);
1295                         n++;
1296                         str = s_cl + 1;
1297
1298                         while (*str && isspace(*(guchar *)str)) str++;
1299                         if (*str != op) {
1300                                 string_list = g_slist_prepend(string_list,
1301                                                               g_strdup(""));
1302                                 n++;
1303                                 s_op = strchr_with_skip_quote(str, '"', op);
1304                                 if (!--max_tokens || !s_op) break;
1305                                 str = s_op;
1306                         } else
1307                                 s_op = str;
1308                         s_cl = strchr_parenthesis_close(str, op, cl);
1309                 } while (--max_tokens && s_cl);
1310         }
1311
1312         str_array = g_new(gchar*, n);
1313
1314         i = n - 1;
1315
1316         str_array[i--] = NULL;
1317         for (slist = string_list; slist; slist = slist->next)
1318                 str_array[i--] = slist->data;
1319
1320         g_slist_free(string_list);
1321
1322         return str_array;
1323 }
1324
1325 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1326                             gint max_tokens)
1327 {
1328         GSList *string_list = NULL, *slist;
1329         gchar **str_array, *s, *new_str;
1330         guint i, n = 1, len;
1331
1332         g_return_val_if_fail(str != NULL, NULL);
1333         g_return_val_if_fail(delim != NULL, NULL);
1334
1335         if (max_tokens < 1)
1336                 max_tokens = G_MAXINT;
1337
1338         s = strstr_with_skip_quote(str, delim);
1339         if (s) {
1340                 guint delimiter_len = strlen(delim);
1341
1342                 do {
1343                         len = s - str;
1344                         new_str = g_strndup(str, len);
1345
1346                         if (new_str[0] == '\'' || new_str[0] == '\"') {
1347                                 if (new_str[len - 1] == new_str[0]) {
1348                                         new_str[len - 1] = '\0';
1349                                         memmove(new_str, new_str + 1, len - 1);
1350                                 }
1351                         }
1352                         string_list = g_slist_prepend(string_list, new_str);
1353                         n++;
1354                         str = s + delimiter_len;
1355                         s = strstr_with_skip_quote(str, delim);
1356                 } while (--max_tokens && s);
1357         }
1358
1359         if (*str) {
1360                 new_str = g_strdup(str);
1361                 if (new_str[0] == '\'' || new_str[0] == '\"') {
1362                         len = strlen(str);
1363                         if (new_str[len - 1] == new_str[0]) {
1364                                 new_str[len - 1] = '\0';
1365                                 memmove(new_str, new_str + 1, len - 1);
1366                         }
1367                 }
1368                 string_list = g_slist_prepend(string_list, new_str);
1369                 n++;
1370         }
1371
1372         str_array = g_new(gchar*, n);
1373
1374         i = n - 1;
1375
1376         str_array[i--] = NULL;
1377         for (slist = string_list; slist; slist = slist->next)
1378                 str_array[i--] = slist->data;
1379
1380         g_slist_free(string_list);
1381
1382         return str_array;
1383 }
1384
1385 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1386 {
1387         gchar *abbrev_group;
1388         gchar *ap;
1389         const gchar *p = group;
1390         const gchar *last;
1391
1392         g_return_val_if_fail(group != NULL, NULL);
1393
1394         last = group + strlen(group);
1395         abbrev_group = ap = g_malloc(strlen(group) + 1);
1396
1397         while (*p) {
1398                 while (*p == '.')
1399                         *ap++ = *p++;
1400                 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1401                         *ap++ = *p++;
1402                         while (*p != '.') p++;
1403                 } else {
1404                         strcpy(ap, p);
1405                         return abbrev_group;
1406                 }
1407         }
1408
1409         *ap = '\0';
1410         return abbrev_group;
1411 }
1412
1413 gchar *trim_string(const gchar *str, gint len)
1414 {
1415         const gchar *p = str;
1416         gint mb_len;
1417         gchar *new_str;
1418         gint new_len = 0;
1419
1420         if (!str) return NULL;
1421         if (strlen(str) <= len)
1422                 return g_strdup(str);
1423         if (g_utf8_validate(str, -1, NULL) == FALSE)
1424                 return g_strdup(str);
1425
1426         while (*p != '\0') {
1427                 mb_len = g_utf8_skip[*(guchar *)p];
1428                 if (mb_len == 0)
1429                         break;
1430                 else if (new_len + mb_len > len)
1431                         break;
1432
1433                 new_len += mb_len;
1434                 p += mb_len;
1435         }
1436
1437         Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1438         return g_strconcat(new_str, "...", NULL);
1439 }
1440
1441 GList *uri_list_extract_filenames(const gchar *uri_list)
1442 {
1443         GList *result = NULL;
1444         const gchar *p, *q;
1445         gchar *escaped_utf8uri;
1446         gchar *file;
1447
1448         p = uri_list;
1449
1450         while (p) {
1451                 if (*p != '#') {
1452                         while (isspace(*p)) p++;
1453                         if (!strncmp(p, "file:", 5)) {
1454                                 q = p;
1455                                 q += 5;
1456                                 while (*q && *q != '\n' && *q != '\r') q++;
1457
1458                                 if (q > p) {
1459                                         gchar *file, *locale_file = NULL;
1460                                         q--;
1461                                         while (q > p && isspace(*q)) q--;
1462                                         Xalloca(escaped_utf8uri, q - p + 2,
1463                                                 return result);
1464                                         Xalloca(file, q - p + 2,
1465                                                 return result);
1466                                         *file = '\0';
1467                                         strncpy(escaped_utf8uri, p, q - p + 1);
1468                                         escaped_utf8uri[q - p + 1] = '\0';
1469                                         decode_uri(file, escaped_utf8uri);
1470 #warning FIXME_GTK2 /* should we use g_filename_from_utf8()? */
1471                     /*
1472                      * g_filename_from_uri() rejects escaped/locale encoded uri
1473                      * string which come from Nautilus.
1474                      */
1475                                         if (g_utf8_validate(file, -1, NULL))
1476                                                 locale_file
1477                                                         = conv_codeset_strdup(
1478                                                                 file + 5,
1479                                                                 CS_UTF_8,
1480                                                                 conv_get_locale_charset_str());
1481                                         if (!locale_file)
1482                                                 locale_file = g_strdup(file + 5);
1483                                         result = g_list_append(result, locale_file);
1484                                 }
1485                         }
1486                 }
1487                 p = strchr(p, '\n');
1488                 if (p) p++;
1489         }
1490
1491         return result;
1492 }
1493
1494 /* Converts two-digit hexadecimal to decimal.  Used for unescaping escaped 
1495  * characters
1496  */
1497 static gint axtoi(const gchar *hexstr)
1498 {
1499         gint hi, lo, result;
1500        
1501         hi = hexstr[0];
1502         if ('0' <= hi && hi <= '9') {
1503                 hi -= '0';
1504         } else
1505                 if ('a' <= hi && hi <= 'f') {
1506                         hi -= ('a' - 10);
1507                 } else
1508                         if ('A' <= hi && hi <= 'F') {
1509                                 hi -= ('A' - 10);
1510                         }
1511
1512         lo = hexstr[1];
1513         if ('0' <= lo && lo <= '9') {
1514                 lo -= '0';
1515         } else
1516                 if ('a' <= lo && lo <= 'f') {
1517                         lo -= ('a'-10);
1518                 } else
1519                         if ('A' <= lo && lo <= 'F') {
1520                                 lo -= ('A' - 10);
1521                         }
1522         result = lo + (16 * hi);
1523         return result;
1524 }
1525
1526 gboolean is_uri_string(const gchar *str)
1527 {
1528         return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1529                 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1530                 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1531                 g_ascii_strncasecmp(str, "www.", 4) == 0);
1532 }
1533
1534 gchar *get_uri_path(const gchar *uri)
1535 {
1536         if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1537                 return (gchar *)(uri + 7);
1538         else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1539                 return (gchar *)(uri + 8);
1540         else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1541                 return (gchar *)(uri + 6);
1542         else
1543                 return (gchar *)uri;
1544 }
1545
1546 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1547  * plusses, and escape characters are used)
1548  */
1549 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1550 {
1551         gchar *dec = decoded_uri;
1552         const gchar *enc = encoded_uri;
1553
1554         while (*enc) {
1555                 if (*enc == '%') {
1556                         enc++;
1557                         if (isxdigit((guchar)enc[0]) &&
1558                             isxdigit((guchar)enc[1])) {
1559                                 *dec = axtoi(enc);
1560                                 dec++;
1561                                 enc += 2;
1562                         }
1563                 } else {
1564                         if (*enc == '+')
1565                                 *dec = ' ';
1566                         else
1567                                 *dec = *enc;
1568                         dec++;
1569                         enc++;
1570                 }
1571         }
1572
1573         *dec = '\0';
1574 }
1575
1576 gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc,
1577                      gchar **subject, gchar **body)
1578 {
1579         gchar *tmp_mailto;
1580         gchar *p;
1581
1582         Xstrdup_a(tmp_mailto, mailto, return -1);
1583
1584         if (!strncmp(tmp_mailto, "mailto:", 7))
1585                 tmp_mailto += 7;
1586
1587         p = strchr(tmp_mailto, '?');
1588         if (p) {
1589                 *p = '\0';
1590                 p++;
1591         }
1592
1593         if (to && !*to)
1594                 *to = g_strdup(tmp_mailto);
1595
1596         while (p) {
1597                 gchar *field, *value;
1598
1599                 field = p;
1600
1601                 p = strchr(p, '=');
1602                 if (!p) break;
1603                 *p = '\0';
1604                 p++;
1605
1606                 value = p;
1607
1608                 p = strchr(p, '&');
1609                 if (p) {
1610                         *p = '\0';
1611                         p++;
1612                 }
1613
1614                 if (*value == '\0') continue;
1615
1616                 if (cc && !*cc && !g_ascii_strcasecmp(field, "cc")) {
1617                         *cc = g_strdup(value);
1618                 } else if (bcc && !*bcc && !g_ascii_strcasecmp(field, "bcc")) {
1619                         *bcc = g_strdup(value);
1620                 } else if (subject && !*subject &&
1621                            !g_ascii_strcasecmp(field, "subject")) {
1622                         *subject = g_malloc(strlen(value) + 1);
1623                         decode_uri(*subject, value);
1624                 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1625                         *body = g_malloc(strlen(value) + 1);
1626                         decode_uri(*body, value);
1627                 }
1628         }
1629
1630         return 0;
1631 }
1632
1633 /*
1634  * We need this wrapper around g_get_home_dir(), so that
1635  * we can fix some Windoze things here.  Should be done in glibc of course
1636  * but as long as we are not able to do our own extensions to glibc, we do
1637  * it here.
1638  */
1639 const gchar *get_home_dir(void)
1640 {
1641 #if HAVE_DOSISH_SYSTEM
1642     static gchar *home_dir;
1643
1644     if (!home_dir) {
1645         home_dir = read_w32_registry_string(NULL,
1646                                             "Software\\Sylpheed", "HomeDir" );
1647         if (!home_dir || !*home_dir) {
1648             if (getenv ("HOMEDRIVE") && getenv("HOMEPATH")) {
1649                 const char *s = g_get_home_dir();
1650                 if (s && *s)
1651                     home_dir = g_strdup (s);
1652             }
1653             if (!home_dir || !*home_dir) 
1654                 home_dir = g_strdup ("c:\\sylpheed");
1655         }
1656         debug_print("initialized home_dir to `%s'\n", home_dir);
1657     }
1658     return home_dir;
1659 #else /* standard glib */
1660     return g_get_home_dir();
1661 #endif
1662 }
1663
1664 const gchar *get_rc_dir(void)
1665 {
1666         static gchar *rc_dir = NULL;
1667
1668         if (!rc_dir)
1669                 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1670                                      RC_DIR, NULL);
1671
1672         return rc_dir;
1673 }
1674
1675 const gchar *get_news_cache_dir(void)
1676 {
1677         static gchar *news_cache_dir = NULL;
1678
1679         if (!news_cache_dir)
1680                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1681                                              NEWS_CACHE_DIR, NULL);
1682
1683         return news_cache_dir;
1684 }
1685
1686 const gchar *get_imap_cache_dir(void)
1687 {
1688         static gchar *imap_cache_dir = NULL;
1689
1690         if (!imap_cache_dir)
1691                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1692                                              IMAP_CACHE_DIR, NULL);
1693
1694         return imap_cache_dir;
1695 }
1696
1697 const gchar *get_mbox_cache_dir(void)
1698 {
1699         static gchar *mbox_cache_dir = NULL;
1700
1701         if (!mbox_cache_dir)
1702                 mbox_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1703                                              MBOX_CACHE_DIR, NULL);
1704
1705         return mbox_cache_dir;
1706 }
1707
1708 const gchar *get_mime_tmp_dir(void)
1709 {
1710         static gchar *mime_tmp_dir = NULL;
1711
1712         if (!mime_tmp_dir)
1713                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1714                                            MIME_TMP_DIR, NULL);
1715
1716         return mime_tmp_dir;
1717 }
1718
1719 const gchar *get_template_dir(void)
1720 {
1721         static gchar *template_dir = NULL;
1722
1723         if (!template_dir)
1724                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1725                                            TEMPLATE_DIR, NULL);
1726
1727         return template_dir;
1728 }
1729
1730 const gchar *get_header_cache_dir(void)
1731 {
1732         static gchar *header_dir = NULL;
1733
1734         if (!header_dir)
1735                 header_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1736                                          HEADER_CACHE_DIR, NULL);
1737
1738         return header_dir;
1739 }
1740
1741 const gchar *get_tmp_dir(void)
1742 {
1743         static gchar *tmp_dir = NULL;
1744
1745         if (!tmp_dir)
1746                 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1747                                       TMP_DIR, NULL);
1748
1749         return tmp_dir;
1750 }
1751
1752 gchar *get_tmp_file(void)
1753 {
1754         gchar *tmp_file;
1755         static guint32 id = 0;
1756
1757         tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1758                                    get_tmp_dir(), G_DIR_SEPARATOR, id++);
1759
1760         return tmp_file;
1761 }
1762
1763 const gchar *get_domain_name(void)
1764 {
1765         static gchar *domain_name = NULL;
1766
1767         if (!domain_name) {
1768                 struct hostent *hp;
1769                 struct utsname uts;
1770
1771                 if (uname(&uts) < 0) {
1772                         perror("gethostname");
1773                         domain_name = "unknown";
1774                 } else {
1775                         if ((hp = my_gethostbyname(uts.nodename)) == NULL) {
1776                                 perror("gethostbyname");
1777                                 domain_name = g_strdup(uts.nodename);
1778                         } else {
1779                                 domain_name = g_strdup(hp->h_name);
1780                         }
1781                 }
1782
1783                 debug_print("domain name = %s\n", domain_name);
1784         }
1785
1786         return domain_name;
1787 }
1788
1789 off_t get_file_size(const gchar *file)
1790 {
1791         struct stat s;
1792
1793         if (stat(file, &s) < 0) {
1794                 FILE_OP_ERROR(file, "stat");
1795                 return -1;
1796         }
1797
1798         return s.st_size;
1799 }
1800
1801 off_t get_file_size_as_crlf(const gchar *file)
1802 {
1803         FILE *fp;
1804         off_t size = 0;
1805         gchar buf[BUFFSIZE];
1806
1807         if ((fp = fopen(file, "rb")) == NULL) {
1808                 FILE_OP_ERROR(file, "fopen");
1809                 return -1;
1810         }
1811
1812         while (fgets(buf, sizeof(buf), fp) != NULL) {
1813                 strretchomp(buf);
1814                 size += strlen(buf) + 2;
1815         }
1816
1817         if (ferror(fp)) {
1818                 FILE_OP_ERROR(file, "fgets");
1819                 size = -1;
1820         }
1821
1822         fclose(fp);
1823
1824         return size;
1825 }
1826
1827 off_t get_left_file_size(FILE *fp)
1828 {
1829         glong pos;
1830         glong end;
1831         off_t size;
1832
1833         if ((pos = ftell(fp)) < 0) {
1834                 perror("ftell");
1835                 return -1;
1836         }
1837         if (fseek(fp, 0L, SEEK_END) < 0) {
1838                 perror("fseek");
1839                 return -1;
1840         }
1841         if ((end = ftell(fp)) < 0) {
1842                 perror("fseek");
1843                 return -1;
1844         }
1845         size = end - pos;
1846         if (fseek(fp, pos, SEEK_SET) < 0) {
1847                 perror("fseek");
1848                 return -1;
1849         }
1850
1851         return size;
1852 }
1853
1854 gboolean file_exist(const gchar *file, gboolean allow_fifo)
1855 {
1856         struct stat s;
1857
1858         if (file == NULL)
1859                 return FALSE;
1860
1861         if (stat(file, &s) < 0) {
1862                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
1863                 return FALSE;
1864         }
1865
1866         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
1867                 return TRUE;
1868
1869         return FALSE;
1870 }
1871
1872 gboolean is_dir_exist(const gchar *dir)
1873 {
1874         struct stat s;
1875
1876         if (dir == NULL)
1877                 return FALSE;
1878
1879         if (stat(dir, &s) < 0) {
1880                 if (ENOENT != errno) FILE_OP_ERROR(dir, "stat");
1881                 return FALSE;
1882         }
1883
1884         if (S_ISDIR(s.st_mode))
1885                 return TRUE;
1886
1887         return FALSE;
1888 }
1889
1890 gboolean is_file_entry_exist(const gchar *file)
1891 {
1892         struct stat s;
1893
1894         if (file == NULL)
1895                 return FALSE;
1896
1897         if (stat(file, &s) < 0) {
1898                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
1899                 return FALSE;
1900         }
1901
1902         return TRUE;
1903 }
1904
1905 gboolean dirent_is_regular_file(struct dirent *d)
1906 {
1907         struct stat s;
1908
1909 #ifdef HAVE_DIRENT_D_TYPE
1910         if (d->d_type == DT_REG)
1911                 return TRUE;
1912         else if (d->d_type != DT_UNKNOWN)
1913                 return FALSE;
1914 #endif
1915
1916         return (stat(d->d_name, &s) == 0 && S_ISREG(s.st_mode));
1917 }
1918
1919 gboolean dirent_is_directory(struct dirent *d)
1920 {
1921         struct stat s;
1922
1923 #ifdef HAVE_DIRENT_D_TYPE
1924         if (d->d_type == DT_DIR)
1925                 return TRUE;
1926         else if (d->d_type != DT_UNKNOWN)
1927                 return FALSE;
1928 #endif
1929
1930         return (stat(d->d_name, &s) == 0 && S_ISDIR(s.st_mode));
1931 }
1932
1933 gint change_dir(const gchar *dir)
1934 {
1935         gchar *prevdir = NULL;
1936
1937         if (debug_mode)
1938                 prevdir = g_get_current_dir();
1939
1940         if (chdir(dir) < 0) {
1941                 FILE_OP_ERROR(dir, "chdir");
1942                 if (debug_mode) g_free(prevdir);
1943                 return -1;
1944         } else if (debug_mode) {
1945                 gchar *cwd;
1946
1947                 cwd = g_get_current_dir();
1948                 if (strcmp(prevdir, cwd) != 0)
1949                         g_print("current dir: %s\n", cwd);
1950                 g_free(cwd);
1951                 g_free(prevdir);
1952         }
1953
1954         return 0;
1955 }
1956
1957 gint make_dir(const gchar *dir)
1958 {
1959         if (mkdir(dir, S_IRWXU) < 0) {
1960                 FILE_OP_ERROR(dir, "mkdir");
1961                 return -1;
1962         }
1963         if (chmod(dir, S_IRWXU) < 0)
1964                 FILE_OP_ERROR(dir, "chmod");
1965
1966         return 0;
1967 }
1968
1969 gint make_dir_hier(const gchar *dir)
1970 {
1971         gchar *parent_dir;
1972         const gchar *p;
1973
1974         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
1975                 parent_dir = g_strndup(dir, p - dir);
1976                 if (*parent_dir != '\0') {
1977                         if (!is_dir_exist(parent_dir)) {
1978                                 if (make_dir(parent_dir) < 0) {
1979                                         g_free(parent_dir);
1980                                         return -1;
1981                                 }
1982                         }
1983                 }
1984                 g_free(parent_dir);
1985         }
1986
1987         if (!is_dir_exist(dir)) {
1988                 if (make_dir(dir) < 0)
1989                         return -1;
1990         }
1991
1992         return 0;
1993 }
1994
1995 gint remove_all_files(const gchar *dir)
1996 {
1997         DIR *dp;
1998         struct dirent *d;
1999         gchar *prev_dir;
2000
2001         prev_dir = g_get_current_dir();
2002
2003         if (chdir(dir) < 0) {
2004                 FILE_OP_ERROR(dir, "chdir");
2005                 g_free(prev_dir);
2006                 return -1;
2007         }
2008
2009         if ((dp = opendir(".")) == NULL) {
2010                 FILE_OP_ERROR(dir, "opendir");
2011                 g_free(prev_dir);
2012                 return -1;
2013         }
2014
2015         while ((d = readdir(dp)) != NULL) {
2016                 if (!strcmp(d->d_name, ".") ||
2017                     !strcmp(d->d_name, ".."))
2018                         continue;
2019
2020                 if (unlink(d->d_name) < 0)
2021                         FILE_OP_ERROR(d->d_name, "unlink");
2022         }
2023
2024         closedir(dp);
2025
2026         if (chdir(prev_dir) < 0) {
2027                 FILE_OP_ERROR(prev_dir, "chdir");
2028                 g_free(prev_dir);
2029                 return -1;
2030         }
2031
2032         g_free(prev_dir);
2033
2034         return 0;
2035 }
2036
2037 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2038 {
2039         DIR *dp;
2040         struct dirent *d;
2041         gchar *prev_dir;
2042         gint file_no;
2043
2044         prev_dir = g_get_current_dir();
2045
2046         if (chdir(dir) < 0) {
2047                 FILE_OP_ERROR(dir, "chdir");
2048                 g_free(prev_dir);
2049                 return -1;
2050         }
2051
2052         if ((dp = opendir(".")) == NULL) {
2053                 FILE_OP_ERROR(dir, "opendir");
2054                 g_free(prev_dir);
2055                 return -1;
2056         }
2057
2058         while ((d = readdir(dp)) != NULL) {
2059                 file_no = to_number(d->d_name);
2060                 if (file_no > 0 && first <= file_no && file_no <= last) {
2061                         if (is_dir_exist(d->d_name))
2062                                 continue;
2063                         if (unlink(d->d_name) < 0)
2064                                 FILE_OP_ERROR(d->d_name, "unlink");
2065                 }
2066         }
2067
2068         closedir(dp);
2069
2070         if (chdir(prev_dir) < 0) {
2071                 FILE_OP_ERROR(prev_dir, "chdir");
2072                 g_free(prev_dir);
2073                 return -1;
2074         }
2075
2076         g_free(prev_dir);
2077
2078         return 0;
2079 }
2080
2081 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2082 {
2083         DIR *dp;
2084         struct dirent *d;
2085         gchar *prev_dir;
2086         gint file_no;
2087
2088         prev_dir = g_get_current_dir();
2089
2090         if (chdir(dir) < 0) {
2091                 FILE_OP_ERROR(dir, "chdir");
2092                 g_free(prev_dir);
2093                 return -1;
2094         }
2095
2096         if ((dp = opendir(".")) == NULL) {
2097                 FILE_OP_ERROR(dir, "opendir");
2098                 g_free(prev_dir);
2099                 return -1;
2100         }
2101
2102         while ((d = readdir(dp)) != NULL) {
2103                 file_no = to_number(d->d_name);
2104                 if (file_no > 0 && (g_slist_find(numberlist, GINT_TO_POINTER(file_no)) == NULL)) {
2105                         debug_print("removing unwanted file %d from %s\n", file_no, dir);
2106                         if (is_dir_exist(d->d_name))
2107                                 continue;
2108                         if (unlink(d->d_name) < 0)
2109                                 FILE_OP_ERROR(d->d_name, "unlink");
2110                 }
2111         }
2112
2113         closedir(dp);
2114
2115         if (chdir(prev_dir) < 0) {
2116                 FILE_OP_ERROR(prev_dir, "chdir");
2117                 g_free(prev_dir);
2118                 return -1;
2119         }
2120
2121         g_free(prev_dir);
2122
2123         return 0;
2124 }
2125
2126 gint remove_all_numbered_files(const gchar *dir)
2127 {
2128         return remove_numbered_files(dir, 0, UINT_MAX);
2129 }
2130
2131 gint remove_expired_files(const gchar *dir, guint hours)
2132 {
2133         DIR *dp;
2134         struct dirent *d;
2135         struct stat s;
2136         gchar *prev_dir;
2137         gint file_no;
2138         time_t mtime, now, expire_time;
2139
2140         prev_dir = g_get_current_dir();
2141
2142         if (chdir(dir) < 0) {
2143                 FILE_OP_ERROR(dir, "chdir");
2144                 g_free(prev_dir);
2145                 return -1;
2146         }
2147
2148         if ((dp = opendir(".")) == NULL) {
2149                 FILE_OP_ERROR(dir, "opendir");
2150                 g_free(prev_dir);
2151                 return -1;
2152         }
2153
2154         now = time(NULL);
2155         expire_time = hours * 60 * 60;
2156
2157         while ((d = readdir(dp)) != NULL) {
2158                 file_no = to_number(d->d_name);
2159                 if (file_no > 0) {
2160                         if (stat(d->d_name, &s) < 0) {
2161                                 FILE_OP_ERROR(d->d_name, "stat");
2162                                 continue;
2163                         }
2164                         if (S_ISDIR(s.st_mode))
2165                                 continue;
2166                         mtime = MAX(s.st_mtime, s.st_atime);
2167                         if (now - mtime > expire_time) {
2168                                 if (unlink(d->d_name) < 0)
2169                                         FILE_OP_ERROR(d->d_name, "unlink");
2170                         }
2171                 }
2172         }
2173
2174         closedir(dp);
2175
2176         if (chdir(prev_dir) < 0) {
2177                 FILE_OP_ERROR(prev_dir, "chdir");
2178                 g_free(prev_dir);
2179                 return -1;
2180         }
2181
2182         g_free(prev_dir);
2183
2184         return 0;
2185 }
2186
2187 gint remove_dir_recursive(const gchar *dir)
2188 {
2189         struct stat s;
2190         DIR *dp;
2191         struct dirent *d;
2192         gchar *prev_dir;
2193
2194         /* g_print("dir = %s\n", dir); */
2195
2196         if (stat(dir, &s) < 0) {
2197                 FILE_OP_ERROR(dir, "stat");
2198                 if (ENOENT == errno) return 0;
2199                 return -1;
2200         }
2201
2202         if (!S_ISDIR(s.st_mode)) {
2203                 if (unlink(dir) < 0) {
2204                         FILE_OP_ERROR(dir, "unlink");
2205                         return -1;
2206                 }
2207
2208                 return 0;
2209         }
2210
2211         prev_dir = g_get_current_dir();
2212         /* g_print("prev_dir = %s\n", prev_dir); */
2213
2214         if (!path_cmp(prev_dir, dir)) {
2215                 g_free(prev_dir);
2216                 if (chdir("..") < 0) {
2217                         FILE_OP_ERROR(dir, "chdir");
2218                         return -1;
2219                 }
2220                 prev_dir = g_get_current_dir();
2221         }
2222
2223         if (chdir(dir) < 0) {
2224                 FILE_OP_ERROR(dir, "chdir");
2225                 g_free(prev_dir);
2226                 return -1;
2227         }
2228
2229         if ((dp = opendir(".")) == NULL) {
2230                 FILE_OP_ERROR(dir, "opendir");
2231                 chdir(prev_dir);
2232                 g_free(prev_dir);
2233                 return -1;
2234         }
2235
2236         /* remove all files in the directory */
2237         while ((d = readdir(dp)) != NULL) {
2238                 if (!strcmp(d->d_name, ".") ||
2239                     !strcmp(d->d_name, ".."))
2240                         continue;
2241
2242                 /* g_print("removing %s\n", d->d_name); */
2243
2244                 if (dirent_is_directory(d)) {
2245                         if (remove_dir_recursive(d->d_name) < 0) {
2246                                 g_warning("can't remove directory\n");
2247                                 return -1;
2248                         }
2249                 } else {
2250                         if (unlink(d->d_name) < 0)
2251                                 FILE_OP_ERROR(d->d_name, "unlink");
2252                 }
2253         }
2254
2255         closedir(dp);
2256
2257         if (chdir(prev_dir) < 0) {
2258                 FILE_OP_ERROR(prev_dir, "chdir");
2259                 g_free(prev_dir);
2260                 return -1;
2261         }
2262
2263         g_free(prev_dir);
2264
2265         if (rmdir(dir) < 0) {
2266                 FILE_OP_ERROR(dir, "rmdir");
2267                 return -1;
2268         }
2269
2270         return 0;
2271 }
2272
2273 #if 0
2274 /* this seems to be slower than the stdio version... */
2275 gint copy_file(const gchar *src, const gchar *dest)
2276 {
2277         gint src_fd, dest_fd;
2278         gint n_read;
2279         gint n_write;
2280         gchar buf[BUFSIZ];
2281         gchar *dest_bak = NULL;
2282
2283         if ((src_fd = open(src, O_RDONLY)) < 0) {
2284                 FILE_OP_ERROR(src, "open");
2285                 return -1;
2286         }
2287
2288         if (is_file_exist(dest)) {
2289                 dest_bak = g_strconcat(dest, ".bak", NULL);
2290                 if (rename(dest, dest_bak) < 0) {
2291                         FILE_OP_ERROR(dest, "rename");
2292                         close(src_fd);
2293                         g_free(dest_bak);
2294                         return -1;
2295                 }
2296         }
2297
2298         if ((dest_fd = open(dest, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
2299                 FILE_OP_ERROR(dest, "open");
2300                 close(src_fd);
2301                 if (dest_bak) {
2302                         if (rename(dest_bak, dest) < 0)
2303                                 FILE_OP_ERROR(dest_bak, "rename");
2304                         g_free(dest_bak);
2305                 }
2306                 return -1;
2307         }
2308
2309         while ((n_read = read(src_fd, buf, sizeof(buf))) > 0) {
2310                 gint len = n_read;
2311                 gchar *bufp = buf;
2312
2313                 while (len > 0) {
2314                         n_write = write(dest_fd, bufp, len);
2315                         if (n_write <= 0) {
2316                                 g_warning("writing to %s failed.\n", dest);
2317                                 close(dest_fd);
2318                                 close(src_fd);
2319                                 unlink(dest);
2320                                 if (dest_bak) {
2321                                         if (rename(dest_bak, dest) < 0)
2322                                                 FILE_OP_ERROR(dest_bak, "rename");
2323                                         g_free(dest_bak);
2324                                 }
2325                                 return -1;
2326                         }
2327                         len -= n_write;
2328                         bufp += n_write;
2329                 }
2330         }
2331
2332         close(src_fd);
2333         close(dest_fd);
2334
2335         if (n_read < 0 || get_file_size(src) != get_file_size(dest)) {
2336                 g_warning("File copy from %s to %s failed.\n", src, dest);
2337                 unlink(dest);
2338                 if (dest_bak) {
2339                         if (rename(dest_bak, dest) < 0)
2340                                 FILE_OP_ERROR(dest_bak, "rename");
2341                         g_free(dest_bak);
2342                 }
2343                 return -1;
2344         }
2345         g_free(dest_bak);
2346
2347         return 0;
2348 }
2349 #endif
2350
2351
2352 /*
2353  * Append src file body to the tail of dest file.
2354  * Now keep_backup has no effects.
2355  */
2356 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2357 {
2358         FILE *src_fp, *dest_fp;
2359         gint n_read;
2360         gchar buf[BUFSIZ];
2361
2362         gboolean err = FALSE;
2363
2364         if ((src_fp = fopen(src, "rb")) == NULL) {
2365                 FILE_OP_ERROR(src, "fopen");
2366                 return -1;
2367         }
2368         
2369         if ((dest_fp = fopen(dest, "ab")) == NULL) {
2370                 FILE_OP_ERROR(dest, "fopen");
2371                 fclose(src_fp);
2372                 return -1;
2373         }
2374
2375         if (change_file_mode_rw(dest_fp, dest) < 0) {
2376                 FILE_OP_ERROR(dest, "chmod");
2377                 g_warning("can't change file mode\n");
2378         }
2379
2380         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2381                 if (n_read < sizeof(buf) && ferror(src_fp))
2382                         break;
2383                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
2384                         g_warning("writing to %s failed.\n", dest);
2385                         fclose(dest_fp);
2386                         fclose(src_fp);
2387                         unlink(dest);
2388                         return -1;
2389                 }
2390         }
2391
2392         if (ferror(src_fp)) {
2393                 FILE_OP_ERROR(src, "fread");
2394                 err = TRUE;
2395         }
2396         fclose(src_fp);
2397         if (fclose(dest_fp) == EOF) {
2398                 FILE_OP_ERROR(dest, "fclose");
2399                 err = TRUE;
2400         }
2401
2402         if (err) {
2403                 unlink(dest);
2404                 return -1;
2405         }
2406
2407         return 0;
2408 }
2409
2410 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2411 {
2412         FILE *src_fp, *dest_fp;
2413         gint n_read;
2414         gchar buf[BUFSIZ];
2415         gchar *dest_bak = NULL;
2416         gboolean err = FALSE;
2417
2418         if ((src_fp = fopen(src, "rb")) == NULL) {
2419                 FILE_OP_ERROR(src, "fopen");
2420                 return -1;
2421         }
2422         if (is_file_exist(dest)) {
2423                 dest_bak = g_strconcat(dest, ".bak", NULL);
2424                 if (rename(dest, dest_bak) < 0) {
2425                         FILE_OP_ERROR(dest, "rename");
2426                         fclose(src_fp);
2427                         g_free(dest_bak);
2428                         return -1;
2429                 }
2430         }
2431
2432         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2433                 FILE_OP_ERROR(dest, "fopen");
2434                 fclose(src_fp);
2435                 if (dest_bak) {
2436                         if (rename(dest_bak, dest) < 0)
2437                                 FILE_OP_ERROR(dest_bak, "rename");
2438                         g_free(dest_bak);
2439                 }
2440                 return -1;
2441         }
2442
2443         if (change_file_mode_rw(dest_fp, dest) < 0) {
2444                 FILE_OP_ERROR(dest, "chmod");
2445                 g_warning("can't change file mode\n");
2446         }
2447
2448         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2449                 if (n_read < sizeof(buf) && ferror(src_fp))
2450                         break;
2451                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
2452                         g_warning("writing to %s failed.\n", dest);
2453                         fclose(dest_fp);
2454                         fclose(src_fp);
2455                         unlink(dest);
2456                         if (dest_bak) {
2457                                 if (rename(dest_bak, dest) < 0)
2458                                         FILE_OP_ERROR(dest_bak, "rename");
2459                                 g_free(dest_bak);
2460                         }
2461                         return -1;
2462                 }
2463         }
2464
2465         if (ferror(src_fp)) {
2466                 FILE_OP_ERROR(src, "fread");
2467                 err = TRUE;
2468         }
2469         fclose(src_fp);
2470         if (fclose(dest_fp) == EOF) {
2471                 FILE_OP_ERROR(dest, "fclose");
2472                 err = TRUE;
2473         }
2474
2475         if (err) {
2476                 unlink(dest);
2477                 if (dest_bak) {
2478                         if (rename(dest_bak, dest) < 0)
2479                                 FILE_OP_ERROR(dest_bak, "rename");
2480                         g_free(dest_bak);
2481                 }
2482                 return -1;
2483         }
2484
2485         if (keep_backup == FALSE && dest_bak)
2486                 unlink(dest_bak);
2487
2488         g_free(dest_bak);
2489
2490         return 0;
2491 }
2492
2493 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2494 {
2495         if (overwrite == FALSE && is_file_exist(dest)) {
2496                 g_warning("move_file(): file %s already exists.", dest);
2497                 return -1;
2498         }
2499
2500         if (rename(src, dest) == 0) return 0;
2501
2502         if (EXDEV != errno) {
2503                 FILE_OP_ERROR(src, "rename");
2504                 return -1;
2505         }
2506
2507         if (copy_file(src, dest, FALSE) < 0) return -1;
2508
2509         unlink(src);
2510
2511         return 0;
2512 }
2513
2514 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2515 {
2516         gint n_read;
2517         gint bytes_left, to_read;
2518         gchar buf[BUFSIZ];
2519
2520         if (fseek(fp, offset, SEEK_SET) < 0) {
2521                 perror("fseek");
2522                 return -1;
2523         }
2524
2525         bytes_left = length;
2526         to_read = MIN(bytes_left, sizeof(buf));
2527
2528         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2529                 if (n_read < to_read && ferror(fp))
2530                         break;
2531                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
2532                         return -1;
2533                 }
2534                 bytes_left -= n_read;
2535                 if (bytes_left == 0)
2536                         break;
2537                 to_read = MIN(bytes_left, sizeof(buf));
2538         }
2539
2540         if (ferror(fp)) {
2541                 perror("fread");
2542                 return -1;
2543         }
2544
2545         return 0;
2546 }
2547
2548 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2549 {
2550         FILE *dest_fp;
2551         gboolean err = FALSE;
2552
2553         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2554                 FILE_OP_ERROR(dest, "fopen");
2555                 return -1;
2556         }
2557
2558         if (change_file_mode_rw(dest_fp, dest) < 0) {
2559                 FILE_OP_ERROR(dest, "chmod");
2560                 g_warning("can't change file mode\n");
2561         }
2562
2563         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2564                 err = TRUE;
2565
2566         if (!err && fclose(dest_fp) == EOF) {
2567                 FILE_OP_ERROR(dest, "fclose");
2568                 err = TRUE;
2569         }
2570
2571         if (err) {
2572                 g_warning("writing to %s failed.\n", dest);
2573                 unlink(dest);
2574                 return -1;
2575         }
2576
2577         return 0;
2578 }
2579
2580 /* convert line endings into CRLF. If the last line doesn't end with
2581  * linebreak, add it.
2582  */
2583 gchar *canonicalize_str(const gchar *str)
2584 {
2585         const gchar *p;
2586         guint new_len = 0;
2587         gchar *out, *outp;
2588
2589         for (p = str; *p != '\0'; ++p) {
2590                 if (*p != '\r') {
2591                         ++new_len;
2592                         if (*p == '\n')
2593                                 ++new_len;
2594                 }
2595         }
2596         if (p == str || *(p - 1) != '\n')
2597                 new_len += 2;
2598
2599         out = outp = g_malloc(new_len + 1);
2600         for (p = str; *p != '\0'; ++p) {
2601                 if (*p != '\r') {
2602                         if (*p == '\n')
2603                                 *outp++ = '\r';
2604                         *outp++ = *p;
2605                 }
2606         }
2607         if (p == str || *(p - 1) != '\n') {
2608                 *outp++ = '\r';
2609                 *outp++ = '\n';
2610         }
2611         *outp = '\0';
2612
2613         return out;
2614 }
2615
2616 gint canonicalize_file(const gchar *src, const gchar *dest)
2617 {
2618         FILE *src_fp, *dest_fp;
2619         gchar buf[BUFFSIZE];
2620         gint len;
2621         gboolean err = FALSE;
2622         gboolean last_linebreak = FALSE;
2623
2624         if ((src_fp = fopen(src, "rb")) == NULL) {
2625                 FILE_OP_ERROR(src, "fopen");
2626                 return -1;
2627         }
2628
2629         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2630                 FILE_OP_ERROR(dest, "fopen");
2631                 fclose(src_fp);
2632                 return -1;
2633         }
2634
2635         if (change_file_mode_rw(dest_fp, dest) < 0) {
2636                 FILE_OP_ERROR(dest, "chmod");
2637                 g_warning("can't change file mode\n");
2638         }
2639
2640         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2641                 gint r = 0;
2642
2643                 len = strlen(buf);
2644                 if (len == 0) break;
2645                 last_linebreak = FALSE;
2646
2647                 if (buf[len - 1] != '\n') {
2648                         last_linebreak = TRUE;
2649                         r = fputs(buf, dest_fp);
2650                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2651                         r = fputs(buf, dest_fp);
2652                 } else {
2653                         if (len > 1) {
2654                                 r = fwrite(buf, len - 1, 1, dest_fp);
2655                                 if (r != 1)
2656                                         r = EOF;
2657                         }
2658                         if (r != EOF)
2659                                 r = fputs("\r\n", dest_fp);
2660                 }
2661
2662                 if (r == EOF) {
2663                         g_warning("writing to %s failed.\n", dest);
2664                         fclose(dest_fp);
2665                         fclose(src_fp);
2666                         unlink(dest);
2667                         return -1;
2668                 }
2669         }
2670
2671         if (last_linebreak == TRUE) {
2672                 if (fputs("\r\n", dest_fp) == EOF)
2673                         err = TRUE;
2674         }
2675
2676         if (ferror(src_fp)) {
2677                 FILE_OP_ERROR(src, "fgets");
2678                 err = TRUE;
2679         }
2680         fclose(src_fp);
2681         if (fclose(dest_fp) == EOF) {
2682                 FILE_OP_ERROR(dest, "fclose");
2683                 err = TRUE;
2684         }
2685
2686         if (err) {
2687                 unlink(dest);
2688                 return -1;
2689         }
2690
2691         return 0;
2692 }
2693
2694 gint canonicalize_file_replace(const gchar *file)
2695 {
2696         gchar *tmp_file;
2697
2698         tmp_file = get_tmp_file();
2699
2700         if (canonicalize_file(file, tmp_file) < 0) {
2701                 g_free(tmp_file);
2702                 return -1;
2703         }
2704
2705         if (move_file(tmp_file, file, TRUE) < 0) {
2706                 g_warning("can't replace %s .\n", file);
2707                 unlink(tmp_file);
2708                 g_free(tmp_file);
2709                 return -1;
2710         }
2711
2712         g_free(tmp_file);
2713         return 0;
2714 }
2715
2716 gint uncanonicalize_file(const gchar *src, const gchar *dest)
2717 {
2718         FILE *src_fp, *dest_fp;
2719         gchar buf[BUFFSIZE];
2720         gboolean err = FALSE;
2721
2722         if ((src_fp = fopen(src, "rb")) == NULL) {
2723                 FILE_OP_ERROR(src, "fopen");
2724                 return -1;
2725         }
2726
2727         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2728                 FILE_OP_ERROR(dest, "fopen");
2729                 fclose(src_fp);
2730                 return -1;
2731         }
2732
2733         if (change_file_mode_rw(dest_fp, dest) < 0) {
2734                 FILE_OP_ERROR(dest, "chmod");
2735                 g_warning("can't change file mode\n");
2736         }
2737
2738         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2739                 strcrchomp(buf);
2740                 if (fputs(buf, dest_fp) == EOF) {
2741                         g_warning("writing to %s failed.\n", dest);
2742                         fclose(dest_fp);
2743                         fclose(src_fp);
2744                         unlink(dest);
2745                         return -1;
2746                 }
2747         }
2748
2749         if (ferror(src_fp)) {
2750                 FILE_OP_ERROR(src, "fgets");
2751                 err = TRUE;
2752         }
2753         fclose(src_fp);
2754         if (fclose(dest_fp) == EOF) {
2755                 FILE_OP_ERROR(dest, "fclose");
2756                 err = TRUE;
2757         }
2758
2759         if (err) {
2760                 unlink(dest);
2761                 return -1;
2762         }
2763
2764         return 0;
2765 }
2766
2767 gint uncanonicalize_file_replace(const gchar *file)
2768 {
2769         gchar *tmp_file;
2770
2771         tmp_file = get_tmp_file();
2772
2773         if (uncanonicalize_file(file, tmp_file) < 0) {
2774                 g_free(tmp_file);
2775                 return -1;
2776         }
2777
2778         if (move_file(tmp_file, file, TRUE) < 0) {
2779                 g_warning("can't replace %s .\n", file);
2780                 unlink(tmp_file);
2781                 g_free(tmp_file);
2782                 return -1;
2783         }
2784
2785         g_free(tmp_file);
2786         return 0;
2787 }
2788
2789 gchar *normalize_newlines(const gchar *str)
2790 {
2791         const gchar *p = str;
2792         gchar *out, *outp;
2793
2794         out = outp = g_malloc(strlen(str) + 1);
2795         for (p = str; *p != '\0'; ++p) {
2796                 if (*p == '\r') {
2797                         if (*(p + 1) != '\n')
2798                                 *outp++ = '\n';
2799                 } else
2800                         *outp++ = *p;
2801         }
2802
2803         *outp = '\0';
2804
2805         return out;
2806 }
2807
2808 gchar *get_outgoing_rfc2822_str(FILE *fp)
2809 {
2810         gchar buf[BUFFSIZE];
2811         GString *str;
2812         gchar *ret;
2813
2814         str = g_string_new(NULL);
2815
2816         /* output header part */
2817         while (fgets(buf, sizeof(buf), fp) != NULL) {
2818                 strretchomp(buf);
2819                 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2820                         gint next;
2821
2822                         for (;;) {
2823                                 next = fgetc(fp);
2824                                 if (next == EOF)
2825                                         break;
2826                                 else if (next != ' ' && next != '\t') {
2827                                         ungetc(next, fp);
2828                                         break;
2829                                 }
2830                                 if (fgets(buf, sizeof(buf), fp) == NULL)
2831                                         break;
2832                         }
2833                 } else {
2834                         g_string_append(str, buf);
2835                         g_string_append(str, "\r\n");
2836                         if (buf[0] == '\0')
2837                                 break;
2838                 }
2839         }
2840
2841         /* output body part */
2842         while (fgets(buf, sizeof(buf), fp) != NULL) {
2843                 strretchomp(buf);
2844                 if (buf[0] == '.')
2845                         g_string_append_c(str, '.');
2846                 g_string_append(str, buf);
2847                 g_string_append(str, "\r\n");
2848         }
2849
2850         ret = str->str;
2851         g_string_free(str, FALSE);
2852
2853         return ret;
2854 }
2855
2856 /*
2857  * Create a new boundary in a way that it is very unlikely that this
2858  * will occur in the following text.  It would be easy to ensure
2859  * uniqueness if everything is either quoted-printable or base64
2860  * encoded (note that conversion is allowed), but because MIME bodies
2861  * may be nested, it may happen that the same boundary has already
2862  * been used. We avoid scanning the message for conflicts and hope the
2863  * best.
2864  *
2865  *   boundary := 0*69<bchars> bcharsnospace
2866  *   bchars := bcharsnospace / " "
2867  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2868  *                    "+" / "_" / "," / "-" / "." /
2869  *                    "/" / ":" / "=" / "?"
2870  *
2871  * some special characters removed because of buggy MTAs
2872  */
2873
2874 gchar *generate_mime_boundary(const gchar *prefix)
2875 {
2876         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2877                              "abcdefghijklmnopqrstuvwxyz"
2878                              "1234567890+_./=";
2879         gchar buf_uniq[17];
2880         gchar buf_date[64];
2881         gint i;
2882         gint pid;
2883
2884         pid = getpid();
2885
2886         /* We make the boundary depend on the pid, so that all running
2887          * processes generate different values even when they have been
2888          * started within the same second and srandom(time(NULL)) has been
2889          * used.  I can't see whether this is really an advantage but it
2890          * doesn't do any harm.
2891          */
2892         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2893                 buf_uniq[i] = tbl[(rand() ^ pid) % (sizeof(tbl) - 1)];
2894         buf_uniq[i] = '\0';
2895
2896         get_rfc822_date(buf_date, sizeof(buf_date));
2897         subst_char(buf_date, ' ', '_');
2898         subst_char(buf_date, ',', '_');
2899         subst_char(buf_date, ':', '_');
2900
2901         return g_strdup_printf("%s_%s_%s", prefix ? prefix : "Multipart",
2902                                buf_date, buf_uniq);
2903 }
2904
2905 gint change_file_mode_rw(FILE *fp, const gchar *file)
2906 {
2907 #if HAVE_FCHMOD
2908         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2909 #else
2910         return chmod(file, S_IRUSR|S_IWUSR);
2911 #endif
2912 }
2913
2914 FILE *my_tmpfile(void)
2915 {
2916 #if HAVE_MKSTEMP
2917         const gchar suffix[] = ".XXXXXX";
2918         const gchar *tmpdir;
2919         guint tmplen;
2920         const gchar *progname;
2921         guint proglen;
2922         gchar *fname;
2923         gint fd;
2924         FILE *fp;
2925
2926         tmpdir = get_tmp_dir();
2927         tmplen = strlen(tmpdir);
2928         progname = g_get_prgname();
2929         if (progname == NULL)
2930                 progname = "sylpheed-claws";
2931         proglen = strlen(progname);
2932         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2933                 return tmpfile());
2934
2935         memcpy(fname, tmpdir, tmplen);
2936         fname[tmplen] = G_DIR_SEPARATOR;
2937         memcpy(fname + tmplen + 1, progname, proglen);
2938         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2939
2940         fd = mkstemp(fname);
2941         if (fd < 0)
2942                 return tmpfile();
2943
2944         unlink(fname);
2945
2946         fp = fdopen(fd, "w+b");
2947         if (!fp)
2948                 close(fd);
2949         else
2950                 return fp;
2951 #endif /* HAVE_MKSTEMP */
2952
2953         return tmpfile();
2954 }
2955
2956 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
2957 {
2958         int fd;
2959         
2960         *filename = g_strdup_printf("%s%csylpheed.XXXXXX", dir, G_DIR_SEPARATOR);
2961         fd = mkstemp(*filename);
2962
2963         return fdopen(fd, "w+");
2964 }
2965
2966 FILE *str_open_as_stream(const gchar *str)
2967 {
2968         FILE *fp;
2969         size_t len;
2970
2971         g_return_val_if_fail(str != NULL, NULL);
2972
2973         fp = my_tmpfile();
2974         if (!fp) {
2975                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
2976                 return NULL;
2977         }
2978
2979         len = strlen(str);
2980         if (len == 0) return fp;
2981
2982         if (fwrite(str, len, 1, fp) != 1) {
2983                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
2984                 fclose(fp);
2985                 return NULL;
2986         }
2987
2988         rewind(fp);
2989         return fp;
2990 }
2991
2992 gint str_write_to_file(const gchar *str, const gchar *file)
2993 {
2994         FILE *fp;
2995         size_t len;
2996
2997         g_return_val_if_fail(str != NULL, -1);
2998         g_return_val_if_fail(file != NULL, -1);
2999
3000         if ((fp = fopen(file, "wb")) == NULL) {
3001                 FILE_OP_ERROR(file, "fopen");
3002                 return -1;
3003         }
3004
3005         len = strlen(str);
3006         if (len == 0) {
3007                 fclose(fp);
3008                 return 0;
3009         }
3010
3011         if (fwrite(str, len, 1, fp) != 1) {
3012                 FILE_OP_ERROR(file, "fwrite");
3013                 fclose(fp);
3014                 unlink(file);
3015                 return -1;
3016         }
3017
3018         if (fclose(fp) == EOF) {
3019                 FILE_OP_ERROR(file, "fclose");
3020                 unlink(file);
3021                 return -1;
3022         }
3023
3024         return 0;
3025 }
3026
3027 gchar *file_read_to_str(const gchar *file)
3028 {
3029         FILE *fp;
3030         gchar *str;
3031
3032         g_return_val_if_fail(file != NULL, NULL);
3033
3034         if ((fp = fopen(file, "rb")) == NULL) {
3035                 FILE_OP_ERROR(file, "fopen");
3036                 return NULL;
3037         }
3038
3039         str = file_read_stream_to_str(fp);
3040
3041         fclose(fp);
3042
3043         return str;
3044 }
3045
3046 gchar *file_read_stream_to_str(FILE *fp)
3047 {
3048         GByteArray *array;
3049         gchar buf[BUFSIZ];
3050         gint n_read;
3051         gchar *str;
3052
3053         g_return_val_if_fail(fp != NULL, NULL);
3054
3055         array = g_byte_array_new();
3056
3057         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3058                 if (n_read < sizeof(buf) && ferror(fp))
3059                         break;
3060                 g_byte_array_append(array, buf, n_read);
3061         }
3062
3063         if (ferror(fp)) {
3064                 FILE_OP_ERROR("file stream", "fread");
3065                 g_byte_array_free(array, TRUE);
3066                 return NULL;
3067         }
3068
3069         buf[0] = '\0';
3070         g_byte_array_append(array, buf, 1);
3071         str = (gchar *)array->data;
3072         g_byte_array_free(array, FALSE);
3073
3074         if (!g_utf8_validate(str, -1, NULL)) {
3075                 const gchar *src_codeset, *dest_codeset;
3076                 gchar *tmp = NULL;
3077                 src_codeset = conv_get_locale_charset_str();
3078                 dest_codeset = CS_UTF_8;
3079                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3080                 g_free(str);
3081                 str = tmp;
3082         }
3083
3084         return str;
3085 }
3086
3087 gint execute_async(gchar *const argv[])
3088 {
3089         pid_t pid;
3090         gint status;
3091
3092         if ((pid = fork()) < 0) {
3093                 perror("fork");
3094                 return -1;
3095         }
3096
3097         if (pid == 0) {                 /* child process */
3098                 pid_t gch_pid;
3099
3100                 if ((gch_pid = fork()) < 0) {
3101                         perror("fork");
3102                         _exit(1);
3103                 }
3104
3105                 if (gch_pid == 0) {     /* grandchild process */
3106                         execvp(argv[0], argv);
3107
3108                         perror("execvp");
3109                         _exit(1);
3110                 }
3111
3112                 _exit(0);
3113         }
3114
3115         waitpid(pid, &status, 0);
3116
3117         if (WIFEXITED(status))
3118                 return WEXITSTATUS(status);
3119         else
3120                 return -1;
3121 }
3122
3123 gint execute_sync(gchar *const argv[])
3124 {
3125         pid_t pid;
3126         gint status;
3127
3128         if ((pid = fork()) < 0) {
3129                 perror("fork");
3130                 return -1;
3131         }
3132
3133         if (pid == 0) {         /* child process */
3134                 execvp(argv[0], argv);
3135
3136                 perror("execvp");
3137                 _exit(1);
3138         }
3139
3140         waitpid(pid, &status, 0);
3141
3142         if (WIFEXITED(status))
3143                 return WEXITSTATUS(status);
3144         else
3145                 return -1;
3146 }
3147
3148 gint execute_command_line(const gchar *cmdline, gboolean async)
3149 {
3150         gchar **argv;
3151         gint ret;
3152
3153         debug_print("executing: %s\n", cmdline);
3154
3155         argv = strsplit_with_quote(cmdline, " ", 0);
3156
3157         if (async)
3158                 ret = execute_async(argv);
3159         else
3160                 ret = execute_sync(argv);
3161
3162         g_strfreev(argv);
3163
3164         return ret;
3165 }
3166
3167 gchar *get_command_output(const gchar *cmdline)
3168 {
3169         gchar buf[BUFFSIZE];
3170         FILE *fp;
3171         GString *str;
3172         gchar *ret;
3173
3174         g_return_val_if_fail(cmdline != NULL, NULL);
3175
3176         if ((fp = popen(cmdline, "r")) == NULL) {
3177                 FILE_OP_ERROR(cmdline, "popen");
3178                 return NULL;
3179         }
3180
3181         str = g_string_new("");
3182
3183         while (fgets(buf, sizeof(buf), fp) != NULL)
3184                 g_string_append(str, buf);
3185
3186         pclose(fp);
3187
3188         ret = str->str;
3189         g_string_free(str, FALSE);
3190
3191         if (!g_utf8_validate(ret, -1, NULL)) {
3192                 const gchar *src_codeset, *dest_codeset;
3193                 gchar *tmp = NULL;
3194                 src_codeset = conv_get_locale_charset_str();
3195                 dest_codeset = CS_UTF_8;
3196                 tmp = conv_codeset_strdup(ret, src_codeset, dest_codeset);
3197                 g_free(ret);
3198                 ret = tmp;
3199         }
3200         
3201         return ret;
3202 }
3203
3204 static gint is_unchanged_uri_char(char c)
3205 {
3206         switch (c) {
3207                 case '(':
3208                 case ')':
3209                 case ',':
3210                         return 0;
3211                 default:
3212                         return 1;
3213         }
3214 }
3215
3216 void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3217 {
3218         int i;
3219         int k;
3220
3221         k = 0;
3222         for(i = 0; i < strlen(uri) ; i++) {
3223                 if (is_unchanged_uri_char(uri[i])) {
3224                         if (k + 2 >= bufsize)
3225                                 break;
3226                         encoded_uri[k++] = uri[i];
3227                 }
3228                 else {
3229                         char * hexa = "0123456789ABCDEF";
3230                         
3231                         if (k + 4 >= bufsize)
3232                                 break;
3233                         encoded_uri[k++] = '%';
3234                         encoded_uri[k++] = hexa[uri[i] / 16];
3235                         encoded_uri[k++] = hexa[uri[i] % 16];
3236                 }
3237         }
3238         encoded_uri[k] = 0;
3239 }
3240
3241 gint open_uri(const gchar *uri, const gchar *cmdline)
3242 {
3243         gchar buf[BUFFSIZE];
3244         gchar *p;
3245         gchar encoded_uri[BUFFSIZE];
3246         
3247         g_return_val_if_fail(uri != NULL, -1);
3248
3249         /* an option to choose whether to use encode_uri or not ? */
3250         encode_uri(encoded_uri, BUFFSIZE, uri);
3251         
3252         if (cmdline &&
3253             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3254             !strchr(p + 2, '%'))
3255                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3256         else {
3257                 if (cmdline)
3258                         g_warning("Open URI command line is invalid "
3259                                   "(there must be only one '%%s'): %s",
3260                                   cmdline);
3261                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3262         }
3263
3264         execute_command_line(buf, TRUE);
3265
3266         return 0;
3267 }
3268
3269 time_t remote_tzoffset_sec(const gchar *zone)
3270 {
3271         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3272         gchar zone3[4];
3273         gchar *p;
3274         gchar c;
3275         gint iustz;
3276         gint offset;
3277         time_t remoteoffset;
3278
3279         strncpy(zone3, zone, 3);
3280         zone3[3] = '\0';
3281         remoteoffset = 0;
3282
3283         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3284             (c == '+' || c == '-')) {
3285                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3286                 if (c == '-')
3287                         remoteoffset = -remoteoffset;
3288         } else if (!strncmp(zone, "UT" , 2) ||
3289                    !strncmp(zone, "GMT", 2)) {
3290                 remoteoffset = 0;
3291         } else if (strlen(zone3) == 3) {
3292                 for (p = ustzstr; *p != '\0'; p += 3) {
3293                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3294                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3295                                 remoteoffset = iustz * 3600;
3296                                 break;
3297                         }
3298                 }
3299                 if (*p == '\0')
3300                         return -1;
3301         } else if (strlen(zone3) == 1) {
3302                 switch (zone[0]) {
3303                 case 'Z': remoteoffset =   0; break;
3304                 case 'A': remoteoffset =  -1; break;
3305                 case 'B': remoteoffset =  -2; break;
3306                 case 'C': remoteoffset =  -3; break;
3307                 case 'D': remoteoffset =  -4; break;
3308                 case 'E': remoteoffset =  -5; break;
3309                 case 'F': remoteoffset =  -6; break;
3310                 case 'G': remoteoffset =  -7; break;
3311                 case 'H': remoteoffset =  -8; break;
3312                 case 'I': remoteoffset =  -9; break;
3313                 case 'K': remoteoffset = -10; break; /* J is not used */
3314                 case 'L': remoteoffset = -11; break;
3315                 case 'M': remoteoffset = -12; break;
3316                 case 'N': remoteoffset =   1; break;
3317                 case 'O': remoteoffset =   2; break;
3318                 case 'P': remoteoffset =   3; break;
3319                 case 'Q': remoteoffset =   4; break;
3320                 case 'R': remoteoffset =   5; break;
3321                 case 'S': remoteoffset =   6; break;
3322                 case 'T': remoteoffset =   7; break;
3323                 case 'U': remoteoffset =   8; break;
3324                 case 'V': remoteoffset =   9; break;
3325                 case 'W': remoteoffset =  10; break;
3326                 case 'X': remoteoffset =  11; break;
3327                 case 'Y': remoteoffset =  12; break;
3328                 default:  remoteoffset =   0; break;
3329                 }
3330                 remoteoffset = remoteoffset * 3600;
3331         } else
3332                 return -1;
3333
3334         return remoteoffset;
3335 }
3336
3337 time_t tzoffset_sec(time_t *now)
3338 {
3339         struct tm gmt, *lt;
3340         gint off;
3341
3342         gmt = *gmtime(now);
3343         lt = localtime(now);
3344
3345         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3346
3347         if (lt->tm_year < gmt.tm_year)
3348                 off -= 24 * 60;
3349         else if (lt->tm_year > gmt.tm_year)
3350                 off += 24 * 60;
3351         else if (lt->tm_yday < gmt.tm_yday)
3352                 off -= 24 * 60;
3353         else if (lt->tm_yday > gmt.tm_yday)
3354                 off += 24 * 60;
3355
3356         if (off >= 24 * 60)             /* should be impossible */
3357                 off = 23 * 60 + 59;     /* if not, insert silly value */
3358         if (off <= -24 * 60)
3359                 off = -(23 * 60 + 59);
3360
3361         return off * 60;
3362 }
3363
3364 /* calculate timezone offset */
3365 gchar *tzoffset(time_t *now)
3366 {
3367         static gchar offset_string[6];
3368         struct tm gmt, *lt;
3369         gint off;
3370         gchar sign = '+';
3371
3372         gmt = *gmtime(now);
3373         lt = localtime(now);
3374
3375         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3376
3377         if (lt->tm_year < gmt.tm_year)
3378                 off -= 24 * 60;
3379         else if (lt->tm_year > gmt.tm_year)
3380                 off += 24 * 60;
3381         else if (lt->tm_yday < gmt.tm_yday)
3382                 off -= 24 * 60;
3383         else if (lt->tm_yday > gmt.tm_yday)
3384                 off += 24 * 60;
3385
3386         if (off < 0) {
3387                 sign = '-';
3388                 off = -off;
3389         }
3390
3391         if (off >= 24 * 60)             /* should be impossible */
3392                 off = 23 * 60 + 59;     /* if not, insert silly value */
3393
3394         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3395
3396         return offset_string;
3397 }
3398
3399 void get_rfc822_date(gchar *buf, gint len)
3400 {
3401         struct tm *lt;
3402         time_t t;
3403         gchar day[4], mon[4];
3404         gint dd, hh, mm, ss, yyyy;
3405
3406         t = time(NULL);
3407         lt = localtime(&t);
3408
3409         sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n",
3410                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3411         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3412                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3413 }
3414
3415 void debug_set_mode(gboolean mode)
3416 {
3417         debug_mode = mode;
3418 }
3419
3420 gboolean debug_get_mode(void)
3421 {
3422         return debug_mode;
3423 }
3424
3425 void debug_print_real(const gchar *format, ...)
3426 {
3427         va_list args;
3428         gchar buf[BUFFSIZE];
3429
3430         if (!debug_mode) return;
3431
3432         va_start(args, format);
3433         g_vsnprintf(buf, sizeof(buf), format, args);
3434         va_end(args);
3435
3436         fputs(buf, stdout);
3437 }
3438
3439 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3440 {
3441         if (subject == NULL)
3442                 subject = "";
3443         else
3444                 subject += subject_get_prefix_length(subject);
3445
3446         return g_hash_table_lookup(subject_table, subject);
3447 }
3448
3449 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3450                           void * data)
3451 {
3452         if (subject == NULL || *subject == 0)
3453                 return;
3454         subject += subject_get_prefix_length(subject);
3455         g_hash_table_insert(subject_table, subject, data);
3456 }
3457
3458 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3459 {
3460         if (subject == NULL)
3461                 return;
3462
3463         subject += subject_get_prefix_length(subject);  
3464         g_hash_table_remove(subject_table, subject);
3465 }
3466
3467 /*!
3468  *\brief        Check if a string is prefixed with known (combinations) 
3469  *              of prefixes. The function assumes that each prefix 
3470  *              is terminated by zero or exactly _one_ space.
3471  *
3472  *\param        str String to check for a prefixes
3473  *
3474  *\return       int Number of chars in the prefix that should be skipped 
3475  *              for a "clean" subject line. If no prefix was found, 0
3476  *              is returned.
3477  */             
3478 int subject_get_prefix_length(const gchar *subject)
3479 {
3480         /*!< Array with allowable reply prefixes regexps. */
3481         static const gchar * const prefixes[] = {
3482                 "Re\\:",                        /* "Re:" */
3483                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3484                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3485                 "Aw\\:",                        /* "Aw:"   (German) */
3486                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3487                 "Res\\:",                       /* "Res:" (Brazilian Outlook) */
3488                 "Fw\\:",                        /* "Fw:" Forward */
3489                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3490                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3491                 "Rif\\:"                        /* "Rif:" (Italian Outlook) */
3492                 /* add more */
3493         };
3494         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3495         int n;
3496         regmatch_t pos;
3497         static regex_t regex;
3498         static gboolean init_;
3499
3500         if (!subject) return 0;
3501         if (!*subject) return 0;
3502
3503         if (!init_) {
3504                 GString *s = g_string_new("");
3505                 
3506                 for (n = 0; n < PREFIXES; n++)
3507                         /* Terminate each prefix regexpression by a
3508                          * "\ ?" (zero or ONE space), and OR them */
3509                         g_string_append_printf(s, "(%s\\ ?)%s",
3510                                           prefixes[n],
3511                                           n < PREFIXES - 1 ? 
3512                                           "|" : "");
3513                 
3514                 g_string_prepend(s, "(");
3515                 g_string_append(s, ")+");       /* match at least once */
3516                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3517                 
3518
3519                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+" 
3520                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3521                 if (regcomp(&regex, s->str, REG_EXTENDED | REG_ICASE)) { 
3522                         debug_print("Error compiling regexp %s\n", s->str);
3523                         g_string_free(s, TRUE);
3524                         return 0;
3525                 } else {
3526                         init_ = TRUE;
3527                         g_string_free(s, TRUE);
3528                 }
3529         }
3530         
3531         if (!regexec(&regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3532                 return pos.rm_eo;
3533         else
3534                 return 0;
3535 }
3536
3537 guint g_stricase_hash(gconstpointer gptr)
3538 {
3539         guint hash_result = 0;
3540         const char *str;
3541
3542         for (str = gptr; str && *str; str++) {
3543                 if (isupper((guchar)*str)) hash_result += (*str + ' ');
3544                 else hash_result += *str;
3545         }
3546
3547         return hash_result;
3548 }
3549
3550 gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3551 {
3552         const char *str1 = gptr1;
3553         const char *str2 = gptr2;
3554
3555         return !g_utf8_collate(str1, str2);
3556 }
3557
3558 gint g_int_compare(gconstpointer a, gconstpointer b)
3559 {
3560         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3561 }
3562
3563 gchar *generate_msgid(gchar *buf, gint len)
3564 {
3565         struct tm *lt;
3566         time_t t;
3567         gchar *addr;
3568
3569         t = time(NULL);
3570         lt = localtime(&t);
3571
3572         addr = g_strconcat("@", get_domain_name(), NULL);
3573
3574         g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3575                    lt->tm_year + 1900, lt->tm_mon + 1,
3576                    lt->tm_mday, lt->tm_hour,
3577                    lt->tm_min, lt->tm_sec,
3578                    (guint) rand(), addr);
3579
3580         g_free(addr);
3581         return buf;
3582 }
3583
3584 /*
3585    quote_cmd_argument()
3586    
3587    return a quoted string safely usable in argument of a command.
3588    
3589    code is extracted and adapted from etPan! project -- DINH V. Hoà.
3590 */
3591
3592 gint quote_cmd_argument(gchar * result, guint size,
3593                         const gchar * path)
3594 {
3595         const gchar * p;
3596         gchar * result_p;
3597         guint remaining;
3598
3599         result_p = result;
3600         remaining = size;
3601
3602         for(p = path ; * p != '\0' ; p ++) {
3603
3604                 if (isalnum((guchar)*p) || (* p == '/')) {
3605                         if (remaining > 0) {
3606                                 * result_p = * p;
3607                                 result_p ++; 
3608                                 remaining --;
3609                         }
3610                         else {
3611                                 result[size - 1] = '\0';
3612                                 return -1;
3613                         }
3614                 }
3615                 else { 
3616                         if (remaining >= 2) {
3617                                 * result_p = '\\';
3618                                 result_p ++; 
3619                                 * result_p = * p;
3620                                 result_p ++; 
3621                                 remaining -= 2;
3622                         }
3623                         else {
3624                                 result[size - 1] = '\0';
3625                                 return -1;
3626                         }
3627                 }
3628         }
3629         if (remaining > 0) {
3630                 * result_p = '\0';
3631         }
3632         else {
3633                 result[size - 1] = '\0';
3634                 return -1;
3635         }
3636   
3637         return 0;
3638 }
3639
3640 typedef struct 
3641 {
3642         GNode           *parent;
3643         GNodeMapFunc     func;
3644         gpointer         data;
3645 } GNodeMapData;
3646
3647 static void g_node_map_recursive(GNode *node, gpointer data)
3648 {
3649         GNodeMapData *mapdata = (GNodeMapData *) data;
3650         GNode *newnode;
3651         GNodeMapData newmapdata;
3652         gpointer newdata;
3653
3654         newdata = mapdata->func(node->data, mapdata->data);
3655         if (newdata != NULL) {
3656                 newnode = g_node_new(newdata);
3657                 g_node_append(mapdata->parent, newnode);
3658
3659                 newmapdata.parent = newnode;
3660                 newmapdata.func = mapdata->func;
3661                 newmapdata.data = mapdata->data;
3662
3663                 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3664         }
3665 }
3666
3667 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3668 {
3669         GNode *root;
3670         GNodeMapData mapdata;
3671
3672         g_return_val_if_fail(node != NULL, NULL);
3673         g_return_val_if_fail(func != NULL, NULL);
3674
3675         root = g_node_new(func(node->data, data));
3676
3677         mapdata.parent = root;
3678         mapdata.func = func;
3679         mapdata.data = data;
3680
3681         g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3682
3683         return root;
3684 }
3685
3686 #define HEX_TO_INT(val, hex)                    \
3687 {                                               \
3688         gchar c = hex;                          \
3689                                                 \
3690         if ('0' <= c && c <= '9') {             \
3691                 val = c - '0';                  \
3692         } else if ('a' <= c && c <= 'f') {      \
3693                 val = c - 'a' + 10;             \
3694         } else if ('A' <= c && c <= 'F') {      \
3695                 val = c - 'A' + 10;             \
3696         } else {                                \
3697                 val = -1;                       \
3698         }                                       \
3699 }
3700
3701 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3702 {
3703         gint hi, lo;
3704
3705         HEX_TO_INT(hi, c1);
3706         HEX_TO_INT(lo, c2);
3707
3708         if (hi == -1 || lo == -1)
3709                 return FALSE;
3710
3711         *out = (hi << 4) + lo;
3712         return TRUE;
3713 }
3714
3715 #define INT_TO_HEX(hex, val)            \
3716 {                                       \
3717         if ((val) < 10)                 \
3718                 hex = '0' + (val);      \
3719         else                            \
3720                 hex = 'A' + (val) - 10; \
3721 }
3722
3723 void get_hex_str(gchar *out, guchar ch)
3724 {
3725         gchar hex;
3726
3727         INT_TO_HEX(hex, ch >> 4);
3728         *out++ = hex;
3729         INT_TO_HEX(hex, ch & 0x0f);
3730         *out++ = hex;
3731 }
3732
3733 #undef REF_DEBUG
3734 #ifndef REF_DEBUG
3735 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3736 #else
3737 #define G_PRINT_REF g_print
3738 #endif
3739
3740 /*!
3741  *\brief        Register ref counted pointer. It is based on GBoxed, so should
3742  *              work with anything that uses the GType system. The semantics
3743  *              are similar to a C++ auto pointer, with the exception that
3744  *              C doesn't have automatic closure (calling destructors) when 
3745  *              exiting a block scope.
3746  *              Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3747  *              function directly.
3748  *
3749  *\return       GType A GType type.
3750  */
3751 GType g_auto_pointer_register(void)
3752 {
3753         static GType auto_pointer_type;
3754         if (!auto_pointer_type)
3755                 auto_pointer_type =
3756                         g_boxed_type_register_static
3757                                 ("G_TYPE_AUTO_POINTER",
3758                                  (GBoxedCopyFunc) g_auto_pointer_copy,
3759                                  (GBoxedFreeFunc) g_auto_pointer_free);
3760         return auto_pointer_type;                                                    
3761 }
3762
3763 /*!
3764  *\brief        Structure with g_new() allocated pointer guarded by the
3765  *              auto pointer
3766  */
3767 typedef struct AutoPointerRef {
3768         void          (*free) (gpointer);
3769         gpointer        pointer;
3770         glong           cnt;
3771 } AutoPointerRef;
3772
3773 /*!
3774  *\brief        The auto pointer opaque structure that references the
3775  *              pointer guard block.
3776  */
3777 typedef struct AutoPointer {
3778         AutoPointerRef *ref;
3779         gpointer        ptr; /*!< access to protected pointer */
3780 } AutoPointer;
3781
3782 /*!
3783  *\brief        Creates an auto pointer for a g_new()ed pointer. Example:
3784  *
3785  *\code 
3786  *
3787  *              ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
3788  *              ... when assigning, copying and freeing storage elements
3789  *
3790  *              gtk_list_store_new(N_S_COLUMNS, 
3791  *                                 G_TYPE_AUTO_POINTER,
3792  *                                 -1);
3793  *
3794  *
3795  *              Template *precious_data = g_new0(Template, 1);
3796  *              g_pointer protect = g_auto_pointer_new(precious_data);
3797  *
3798  *              gtk_list_store_set(container, &iter,
3799  *                                 S_DATA, protect,
3800  *                                 -1);
3801  *
3802  *              ... the gtk_list_store has copied the pointer and 
3803  *              ... incremented its reference count, we should free
3804  *              ... the auto pointer (in C++ a destructor would do
3805  *              ... this for us when leaving block scope)
3806  * 
3807  *              g_auto_pointer_free(protect);
3808  *
3809  *              ... gtk_list_store_set() now manages the data. When
3810  *              ... *explicitly* requesting a pointer from the list 
3811  *              ... store, don't forget you get a copy that should be 
3812  *              ... freed with g_auto_pointer_free() eventually.
3813  *
3814  *\endcode
3815  *
3816  *\param        pointer Pointer to be guarded.
3817  *
3818  *\return       GAuto * Pointer that should be used in containers with
3819  *              GType support.
3820  */
3821 GAuto *g_auto_pointer_new(gpointer p)
3822 {
3823         AutoPointerRef *ref;
3824         AutoPointer    *ptr;
3825         
3826         if (p == NULL) 
3827                 return NULL;
3828
3829         ref = g_new0(AutoPointerRef, 1);
3830         ptr = g_new0(AutoPointer, 1);
3831
3832         ref->pointer = p;
3833         ref->free = g_free;
3834         ref->cnt = 1;
3835
3836         ptr->ref = ref;
3837         ptr->ptr = p;
3838
3839         G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
3840
3841         return ptr;
3842 }
3843
3844 /*!
3845  *\brief        Allocate an autopointer using the passed \a free function to
3846  *              free the guarded pointer
3847  */
3848 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
3849 {
3850         AutoPointer *aptr;
3851         
3852         if (p == NULL)
3853                 return NULL;
3854
3855         aptr = g_auto_pointer_new(p);
3856         aptr->ref->free = free_;
3857         return aptr; 
3858 }
3859
3860 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
3861 {
3862         if (auto_ptr == NULL) 
3863                 return NULL;
3864         return ((AutoPointer *) auto_ptr)->ptr; 
3865 }
3866
3867 /*!
3868  *\brief        Copies an auto pointer by. It's mostly not necessary
3869  *              to call this function directly, unless you copy/assign
3870  *              the guarded pointer.
3871  *
3872  *\param        auto_ptr Auto pointer returned by previous call to 
3873  *              g_auto_pointer_new_XXX()
3874  *
3875  *\return       gpointer An auto pointer
3876  */
3877 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
3878 {
3879         AutoPointer     *ptr;
3880         AutoPointerRef  *ref;
3881         AutoPointer     *newp;
3882
3883         if (auto_ptr == NULL) 
3884                 return NULL;
3885
3886         ptr = auto_ptr;
3887         ref = ptr->ref;
3888         newp = g_new0(AutoPointer, 1);
3889
3890         newp->ref = ref;
3891         newp->ptr = ref->pointer;
3892         ++(ref->cnt);
3893         
3894         G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
3895
3896         return newp;
3897 }
3898
3899 /*!
3900  *\brief        Free an auto pointer
3901  */
3902 void g_auto_pointer_free(GAuto *auto_ptr)
3903 {
3904         AutoPointer     *ptr;
3905         AutoPointerRef  *ref;
3906         
3907         if (auto_ptr == NULL)
3908                 return;
3909
3910         ptr = auto_ptr;
3911         ref = ptr->ref;
3912
3913         if (--(ref->cnt) == 0) {
3914                 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
3915                 ref->free(ref->pointer);
3916                 g_free(ref);
3917         } else
3918                 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
3919         g_free(ptr);            
3920 }
3921