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