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