9cf692566199e4e3f816ffcb88e7494025b88dbe
[claws.git] / src / utils.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2002 Hiroyuki Yamamoto
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
45 #include "intl.h"
46 #include "utils.h"
47 #include "statusbar.h"
48 #include "logwindow.h"
49
50 #define BUFFSIZE        8192
51
52 extern gboolean debug_mode;
53
54 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data);
55
56 void list_free_strings(GList *list)
57 {
58         list = g_list_first(list);
59
60         while (list != NULL) {
61                 g_free(list->data);
62                 list = list->next;
63         }
64 }
65
66 void slist_free_strings(GSList *list)
67 {
68         while (list != NULL) {
69                 g_free(list->data);
70                 list = list->next;
71         }
72 }
73
74 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
75 {
76         g_free(key);
77 }
78
79 void hash_free_strings(GHashTable *table)
80 {
81         g_hash_table_foreach(table, hash_free_strings_func, NULL);
82 }
83
84 static void hash_free_value_mem_func(gpointer key, gpointer value,
85                                      gpointer data)
86 {
87         g_free(value);
88 }
89
90 void hash_free_value_mem(GHashTable *table)
91 {
92         g_hash_table_foreach(table, hash_free_value_mem_func, NULL);
93 }
94
95 void ptr_array_free_strings(GPtrArray *array)
96 {
97         gint i;
98         gchar *str;
99
100         g_return_if_fail(array != NULL);
101
102         for (i = 0; i < array->len; i++) {
103                 str = g_ptr_array_index(array, i);
104                 g_free(str);
105         }
106 }
107
108 gint to_number(const gchar *nstr)
109 {
110         register const gchar *p;
111
112         if (*nstr == '\0') return -1;
113
114         for (p = nstr; *p != '\0'; p++)
115                 if (!isdigit(*p)) return -1;
116
117         return atoi(nstr);
118 }
119
120 /* convert integer into string,
121    nstr must be not lower than 11 characters length */
122 gchar *itos_buf(gchar *nstr, gint n)
123 {
124         g_snprintf(nstr, 11, "%d", n);
125         return nstr;
126 }
127
128 /* convert integer into string */
129 gchar *itos(gint n)
130 {
131         static gchar nstr[11];
132
133         return itos_buf(nstr, n);
134 }
135
136 gchar *to_human_readable(off_t size)
137 {
138         static gchar str[10];
139
140         if (size < 1024)
141                 g_snprintf(str, sizeof(str), "%dB", (gint)size);
142         else if (size >> 10 < 1024)
143                 g_snprintf(str, sizeof(str), "%.1fKB", (gfloat)size / (1 << 10));
144         else if (size >> 20 < 1024)
145                 g_snprintf(str, sizeof(str), "%.2fMB", (gfloat)size / (1 << 20));
146         else
147                 g_snprintf(str, sizeof(str), "%.2fGB", (gfloat)size / (1 << 30));
148
149         return str;
150 }
151
152 /* strcmp with NULL-checking */
153 gint strcmp2(const gchar *s1, const gchar *s2)
154 {
155         if (s1 == NULL || s2 == NULL)
156                 return -1;
157         else
158                 return strcmp(s1, s2);
159 }
160 /* strstr with NULL-checking */
161 gchar *strstr2(const gchar *s1, const gchar *s2)
162 {
163         if (s1 == NULL || s2 == NULL)
164                 return NULL;
165         else
166                 return strstr(s1, s2);
167 }
168 /* compare paths */
169 gint path_cmp(const gchar *s1, const gchar *s2)
170 {
171         gint len1, len2;
172
173         if (s1 == NULL || s2 == NULL) return -1;
174         if (*s1 == '\0' || *s2 == '\0') return -1;
175
176         len1 = strlen(s1);
177         len2 = strlen(s2);
178
179         if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
180         if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
181
182         return strncmp(s1, s2, MAX(len1, len2));
183 }
184
185 /* remove trailing return code */
186 gchar *strretchomp(gchar *str)
187 {
188         register gchar *s;
189
190         if (!*str) return str;
191
192         for (s = str + strlen(str) - 1;
193              s >= str && (*s == '\n' || *s == '\r');
194              s--)
195                 *s = '\0';
196
197         return str;
198 }
199
200 /* remove trailing character */
201 gchar *strtailchomp(gchar *str, gchar tail_char)
202 {
203         register gchar *s;
204
205         if (!*str) return str;
206         if (tail_char == '\0') return str;
207
208         for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
209                 *s = '\0';
210
211         return str;
212 }
213
214 /* remove CR (carriage return) */
215 gchar *strcrchomp(gchar *str)
216 {
217         register gchar *s;
218
219         if (!*str) return str;
220
221         s = str + strlen(str) - 1;
222         if (*s == '\n' && s > str && *(s - 1) == '\r') {
223                 *(s - 1) = '\n';
224                 *s = '\0';
225         }
226
227         return str;
228 }
229
230 /* Similar to `strstr' but this function ignores the case of both strings.  */
231 gchar *strcasestr(const gchar *haystack, const gchar *needle)
232 {
233         register size_t haystack_len, needle_len;
234
235         haystack_len = strlen(haystack);
236         needle_len   = strlen(needle);
237
238         if (haystack_len < needle_len || needle_len == 0)
239                 return NULL;
240
241         while (haystack_len >= needle_len) {
242                 if (!strncasecmp(haystack, needle, needle_len))
243                         return (gchar *)haystack;
244                 else {
245                         haystack++;
246                         haystack_len--;
247                 }
248         }
249
250         return NULL;
251 }
252
253 /* Copy no more than N characters of SRC to DEST, with NULL terminating.  */
254 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
255 {
256         register gchar c;
257         gchar *s = dest;
258
259         do {
260                 if (--n == 0) {
261                         *dest = '\0';
262                         return s;
263                 }
264                 c = *src++;
265                 *dest++ = c;
266         } while (c != '\0');
267
268         /* don't do zero fill */
269         return s;
270 }
271
272 #if !HAVE_ISWALNUM
273 int iswalnum(wint_t wc)
274 {
275         return isalnum((int)wc);
276 }
277 #endif
278
279 #if !HAVE_ISWSPACE
280 int iswspace(wint_t wc)
281 {
282         return isspace((int)wc);
283 }
284 #endif
285
286 #if !HAVE_TOWLOWER
287 wint_t towlower(wint_t wc)
288 {
289         if (wc >= L'A' && wc <= L'Z')
290                 return wc + L'a' - L'A';
291
292         return wc;
293 }
294 #endif
295
296 #if !HAVE_WCSLEN
297 size_t wcslen(const wchar_t *s)
298 {
299         size_t len = 0;
300
301         while (*s != L'\0')
302                 ++len, ++s;
303
304         return len;
305 }
306 #endif
307
308 #if !HAVE_WCSCPY
309 /* Copy SRC to DEST.  */
310 wchar_t *wcscpy(wchar_t *dest, const wchar_t *src)
311 {
312         wint_t c;
313         wchar_t *s = dest;
314
315         do {
316                 c = *src++;
317                 *dest++ = c;
318         } while (c != L'\0');
319
320         return s;
321 }
322 #endif
323
324 #if !HAVE_WCSNCPY
325 /* Copy no more than N wide-characters of SRC to DEST.  */
326 wchar_t *wcsncpy (wchar_t *dest, const wchar_t *src, size_t n)
327 {
328         wint_t c;
329         wchar_t *s = dest;
330
331         do {
332                 c = *src++;
333                 *dest++ = c;
334                 if (--n == 0)
335                         return s;
336         } while (c != L'\0');
337
338         /* zero fill */
339         do
340                 *dest++ = L'\0';
341         while (--n > 0);
342
343         return s;
344 }
345 #endif
346
347 /* Duplicate S, returning an identical malloc'd string. */
348 wchar_t *wcsdup(const wchar_t *s)
349 {
350         wchar_t *new_str;
351
352         if (s) {
353                 new_str = g_new(wchar_t, wcslen(s) + 1);
354                 wcscpy(new_str, s);
355         } else
356                 new_str = NULL;
357
358         return new_str;
359 }
360
361 /* Duplicate no more than N wide-characters of S,
362    returning an identical malloc'd string. */
363 wchar_t *wcsndup(const wchar_t *s, size_t n)
364 {
365         wchar_t *new_str;
366
367         if (s) {
368                 new_str = g_new(wchar_t, n + 1);
369                 wcsncpy(new_str, s, n);
370                 new_str[n] = (wchar_t)0;
371         } else
372                 new_str = NULL;
373
374         return new_str;
375 }
376
377 wchar_t *strdup_mbstowcs(const gchar *s)
378 {
379         wchar_t *new_str;
380
381         if (s) {
382                 new_str = g_new(wchar_t, strlen(s) + 1);
383                 if (mbstowcs(new_str, s, strlen(s) + 1) < 0) {
384                         g_free(new_str);
385                         new_str = NULL;
386                 } else
387                         new_str = g_realloc(new_str,
388                                             sizeof(wchar_t) * (wcslen(new_str) + 1));
389         } else
390                 new_str = NULL;
391
392         return new_str;
393 }
394
395 gchar *strdup_wcstombs(const wchar_t *s)
396 {
397         gchar *new_str;
398         size_t len;
399
400         if (s) {
401                 len = wcslen(s) * MB_CUR_MAX + 1;
402                 new_str = g_new(gchar, len);
403                 if (wcstombs(new_str, s, len) < 0) {
404                         g_free(new_str);
405                         new_str = NULL;
406                 } else
407                         new_str = g_realloc(new_str, strlen(new_str) + 1);
408         } else
409                 new_str = NULL;
410
411         return new_str;
412 }
413
414 /* Compare S1 and S2, ignoring case.  */
415 gint wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n)
416 {
417         wint_t c1;
418         wint_t c2;
419
420         while (n--) {
421                 c1 = towlower(*s1++);
422                 c2 = towlower(*s2++);
423                 if (c1 != c2)
424                         return c1 - c2;
425                 else if (c1 == 0 && c2 == 0)
426                         break;
427         }
428
429         return 0;
430 }
431
432 /* Find the first occurrence of NEEDLE in HAYSTACK, ignoring case.  */
433 wchar_t *wcscasestr(const wchar_t *haystack, const wchar_t *needle)
434 {
435         register size_t haystack_len, needle_len;
436
437         haystack_len = wcslen(haystack);
438         needle_len   = wcslen(needle);
439
440         if (haystack_len < needle_len || needle_len == 0)
441                 return NULL;
442
443         while (haystack_len >= needle_len) {
444                 if (!wcsncasecmp(haystack, needle, needle_len))
445                         return (wchar_t *)haystack;
446                 else {
447                         haystack++;
448                         haystack_len--;
449                 }
450         }
451
452         return NULL;
453 }
454
455 /* Examine if next block is non-ASCII string */
456 gboolean is_next_nonascii(const wchar_t *s)
457 {
458         const wchar_t *wp;
459
460         /* skip head space */
461         for (wp = s; *wp != (wchar_t)0 && iswspace(*wp); wp++)
462                 ;
463         for (; *wp != (wchar_t)0 && !iswspace(*wp); wp++) {
464                 if (*wp > 127)
465                         return TRUE;
466         }
467
468         return FALSE;
469 }
470
471 /* Examine if next block is multi-byte string */
472 gboolean is_next_mbs(const wchar_t *s)
473 {
474         gint mbl;
475         const wchar_t *wp;
476         gchar tmp[MB_LEN_MAX];
477
478         /* skip head space */
479         for (wp = s; *wp != (wchar_t)0 && iswspace(*wp); wp++)
480                 ;
481         for (; *wp != (wchar_t)0 && !iswspace(*wp); wp++) {
482                 mbl = wctomb(tmp, *wp);
483                 if (mbl > 1)
484                         return TRUE;
485         }
486
487         return FALSE;
488 }
489
490 wchar_t *find_wspace(const wchar_t *s)
491 {
492         const wchar_t *wp;
493
494         for (wp = s; *wp != (wchar_t)0 && iswspace(*wp); wp++)
495                 ;
496         for (; *wp != (wchar_t)0; wp++) {
497                 if (iswspace(*wp))
498                         return (wchar_t *)wp;
499         }
500
501         return NULL;
502 }
503
504 /* compare subjects */
505 gint subject_compare(const gchar *s1, const gchar *s2)
506 {
507         gchar *str1, *str2;
508
509         if (!s1 || !s2) return -1;
510         if (!*s1 || !*s2) return -1;
511
512         Xstrdup_a(str1, s1, return -1);
513         Xstrdup_a(str2, s2, return -1);
514
515         trim_subject(str1);
516         trim_subject(str2);
517
518         if (!*str1 || !*str2) return -1;
519
520         return strcmp(str1, str2);
521 }
522
523 void trim_subject(gchar *str)
524 {
525         gchar *srcp;
526
527         eliminate_parenthesis(str, '[', ']');
528         eliminate_parenthesis(str, '(', ')');
529         g_strstrip(str);
530
531         while (!strncasecmp(str, "Re:", 3)) {
532                 srcp = str + 3;
533                 while (isspace(*srcp)) srcp++;
534                 memmove(str, srcp, strlen(srcp) + 1);
535         }
536 }
537
538 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
539 {
540         register gchar *srcp, *destp;
541         gint in_brace;
542
543         srcp = destp = str;
544
545         while ((destp = strchr(destp, op))) {
546                 in_brace = 1;
547                 srcp = destp + 1;
548                 while (*srcp) {
549                         if (*srcp == op)
550                                 in_brace++;
551                         else if (*srcp == cl)
552                                 in_brace--;
553                         srcp++;
554                         if (in_brace == 0)
555                                 break;
556                 }
557                 while (isspace(*srcp)) srcp++;
558                 memmove(destp, srcp, strlen(srcp) + 1);
559         }
560 }
561
562 void extract_parenthesis(gchar *str, gchar op, gchar cl)
563 {
564         register gchar *srcp, *destp;
565         gint in_brace;
566
567         srcp = destp = str;
568
569         while ((srcp = strchr(destp, op))) {
570                 if (destp > str)
571                         *destp++ = ' ';
572                 memmove(destp, srcp + 1, strlen(srcp));
573                 in_brace = 1;
574                 while(*destp) {
575                         if (*destp == op)
576                                 in_brace++;
577                         else if (*destp == cl)
578                                 in_brace--;
579
580                         if (in_brace == 0)
581                                 break;
582
583                         destp++;
584                 }
585         }
586         *destp = '\0';
587 }
588
589 void extract_one_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
590                                              gchar op, gchar cl)
591 {
592         register gchar *srcp, *destp;
593         gint in_brace;
594         gboolean in_quote = FALSE;
595
596         srcp = destp = str;
597
598         if ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
599                 memmove(destp, srcp + 1, strlen(srcp));
600                 in_brace = 1;
601                 while(*destp) {
602                         if (*destp == op && !in_quote)
603                                 in_brace++;
604                         else if (*destp == cl && !in_quote)
605                                 in_brace--;
606                         else if (*destp == quote_chr)
607                                 in_quote ^= TRUE;
608
609                         if (in_brace == 0)
610                                 break;
611
612                         destp++;
613                 }
614         }
615         *destp = '\0';
616 }
617
618 void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
619                                          gchar op, gchar cl)
620 {
621         register gchar *srcp, *destp;
622         gint in_brace;
623         gboolean in_quote = FALSE;
624
625         srcp = destp = str;
626
627         while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
628                 if (destp > str)
629                         *destp++ = ' ';
630                 memmove(destp, srcp + 1, strlen(srcp));
631                 in_brace = 1;
632                 while(*destp) {
633                         if (*destp == op && !in_quote)
634                                 in_brace++;
635                         else if (*destp == cl && !in_quote)
636                                 in_brace--;
637                         else if (*destp == quote_chr)
638                                 in_quote ^= TRUE;
639
640                         if (in_brace == 0)
641                                 break;
642
643                         destp++;
644                 }
645         }
646         *destp = '\0';
647 }
648
649 void eliminate_quote(gchar *str, gchar quote_chr)
650 {
651         register gchar *srcp, *destp;
652
653         srcp = destp = str;
654
655         while ((destp = strchr(destp, quote_chr))) {
656                 if ((srcp = strchr(destp + 1, quote_chr))) {
657                         srcp++;
658                         while (isspace(*srcp)) srcp++;
659                         memmove(destp, srcp, strlen(srcp) + 1);
660                 } else {
661                         *destp = '\0';
662                         break;
663                 }
664         }
665 }
666
667 void extract_quote(gchar *str, gchar quote_chr)
668 {
669         register gchar *p;
670
671         if ((str = strchr(str, quote_chr))) {
672                 p = str;
673                 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
674                         memmove(p - 1, p, strlen(p) + 1);
675                         p--;
676                 }
677                 if(p) {
678                         *p = '\0';
679                         memmove(str, str + 1, p - str);
680                 }
681         }
682 }
683
684 void eliminate_address_comment(gchar *str)
685 {
686         register gchar *srcp, *destp;
687         gint in_brace;
688
689         srcp = destp = str;
690
691         while ((destp = strchr(destp, '"'))) {
692                 if ((srcp = strchr(destp + 1, '"'))) {
693                         srcp++;
694                         if (*srcp == '@') {
695                                 destp = srcp + 1;
696                         } else {
697                                 while (isspace(*srcp)) srcp++;
698                                 memmove(destp, srcp, strlen(srcp) + 1);
699                         }
700                 } else {
701                         *destp = '\0';
702                         break;
703                 }
704         }
705
706         srcp = destp = str;
707
708         while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
709                 in_brace = 1;
710                 srcp = destp + 1;
711                 while (*srcp) {
712                         if (*srcp == '(')
713                                 in_brace++;
714                         else if (*srcp == ')')
715                                 in_brace--;
716                         srcp++;
717                         if (in_brace == 0)
718                                 break;
719                 }
720                 while (isspace(*srcp)) srcp++;
721                 memmove(destp, srcp, strlen(srcp) + 1);
722         }
723 }
724
725 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
726 {
727         gboolean in_quote = FALSE;
728
729         while (*str) {
730                 if (*str == c && !in_quote)
731                         return (gchar *)str;
732                 if (*str == quote_chr)
733                         in_quote ^= TRUE;
734                 str++;
735         }
736
737         return NULL;
738 }
739
740 gchar *strrchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
741 {
742         gboolean in_quote = FALSE;
743         const gchar *p;
744
745         p = str + strlen(str) - 1;
746         while (p >= str) {
747                 if (*p == c && !in_quote)
748                         return (gchar *)p;
749                 if (*p == quote_chr)
750                         in_quote ^= TRUE;
751                 p--;
752         }
753
754         return NULL;
755 }
756
757 void extract_address(gchar *str)
758 {
759         eliminate_address_comment(str);
760         if (strchr_with_skip_quote(str, '"', '<'))
761                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
762         g_strstrip(str);
763 }
764
765 GSList *address_list_append(GSList *addr_list, const gchar *str)
766 {
767         gchar *work;
768         gchar *workp;
769
770         if (!str) return addr_list;
771
772         Xstrdup_a(work, str, return addr_list);
773
774         eliminate_address_comment(work);
775         workp = work;
776
777         while (workp && *workp) {
778                 gchar *p, *next;
779
780                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
781                         *p = '\0';
782                         next = p + 1;
783                 } else
784                         next = NULL;
785
786                 if (strchr_with_skip_quote(workp, '"', '<'))
787                         extract_parenthesis_with_skip_quote
788                                 (workp, '"', '<', '>');
789
790                 g_strstrip(workp);
791                 if (*workp)
792                         addr_list = g_slist_append(addr_list, g_strdup(workp));
793
794                 workp = next;
795         }
796
797         return addr_list;
798 }
799
800 GSList *references_list_append(GSList *msgid_list, const gchar *str)
801 {
802         const gchar *strp;
803
804         if (!str) return msgid_list;
805         strp = str;
806
807         while (strp && *strp) {
808                 const gchar *start, *end;
809                 gchar *msgid;
810
811                 if ((start = strchr(strp, '<')) != NULL) {
812                         end = strchr(start + 1, '>');
813                         if (!end) break;
814                 } else
815                         break;
816
817                 msgid = g_strndup(start + 1, end - start - 1);
818                 g_strstrip(msgid);
819                 if (*msgid)
820                         msgid_list = g_slist_append(msgid_list, msgid);
821                 else
822                         g_free(msgid);
823
824                 strp = end + 1;
825         }
826
827         return msgid_list;
828 }
829
830 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
831 {
832         gchar *work;
833         gchar *workp;
834
835         if (!str) return group_list;
836
837         Xstrdup_a(work, str, return group_list);
838
839         workp = work;
840
841         while (workp && *workp) {
842                 gchar *p, *next;
843
844                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
845                         *p = '\0';
846                         next = p + 1;
847                 } else
848                         next = NULL;
849
850                 g_strstrip(workp);
851                 if (*workp)
852                         group_list = g_slist_append(group_list,
853                                                     g_strdup(workp));
854
855                 workp = next;
856         }
857
858         return group_list;
859 }
860
861 GList *add_history(GList *list, const gchar *str)
862 {
863         GList *old;
864
865         g_return_val_if_fail(str != NULL, list);
866
867         old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
868         if (old) {
869                 g_free(old->data);
870                 list = g_list_remove(list, old->data);
871         } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
872                 GList *last;
873
874                 last = g_list_last(list);
875                 if (last) {
876                         g_free(last->data);
877                         g_list_remove(list, last->data);
878                 }
879         }
880
881         list = g_list_prepend(list, g_strdup(str));
882
883         return list;
884 }
885
886 void remove_return(gchar *str)
887 {
888         register gchar *p = str;
889
890         while (*p) {
891                 if (*p == '\n' || *p == '\r')
892                         memmove(p, p + 1, strlen(p));
893                 else
894                         p++;
895         }
896 }
897
898 void remove_space(gchar *str)
899 {
900         register gchar *p = str;
901         register gint spc;
902
903         while (*p) {
904                 spc = 0;
905                 while (isspace(*(p + spc)))
906                         spc++;
907                 if (spc)
908                         memmove(p, p + spc, strlen(p + spc) + 1);
909                 else
910                         p++;
911         }
912 }
913
914 void unfold_line(gchar *str)
915 {
916         register gchar *p = str;
917         register gint spc;
918
919         while (*p) {
920                 if (*p == '\n' || *p == '\r') {
921                         *p++ = ' ';
922                         spc = 0;
923                         while (isspace(*(p + spc)))
924                                 spc++;
925                         if (spc)
926                                 memmove(p, p + spc, strlen(p + spc) + 1);
927                 } else
928                         p++;
929         }
930 }
931
932 void subst_char(gchar *str, gchar orig, gchar subst)
933 {
934         register gchar *p = str;
935
936         while (*p) {
937                 if (*p == orig)
938                         *p = subst;
939                 p++;
940         }
941 }
942
943 void subst_chars(gchar *str, gchar *orig, gchar subst)
944 {
945         register gchar *p = str;
946
947         while (*p) {
948                 if (strchr(orig, *p) != NULL)
949                         *p = subst;
950                 p++;
951         }
952 }
953
954 void subst_for_filename(gchar *str)
955 {
956         subst_chars(str, " \t\r\n\"/\\", '_');
957 }
958
959 gboolean is_header_line(const gchar *str)
960 {
961         if (str[0] == ':') return FALSE;
962
963         while (*str != '\0' && *str != ' ') {
964                 if (*str == ':')
965                         return TRUE;
966                 str++;
967         }
968
969         return FALSE;
970 }
971
972 gboolean is_ascii_str(const guchar *str)
973 {
974         while (*str != '\0') {
975                 if (*str != '\t' && *str != ' ' &&
976                     *str != '\r' && *str != '\n' &&
977                     (*str < 32 || *str >= 127))
978                         return FALSE;
979                 str++;
980         }
981
982         return TRUE;
983 }
984
985 gint get_quote_level(const gchar *str)
986 {
987         const gchar *first_pos;
988         const gchar *last_pos;
989         const gchar *p = str;
990         gint quote_level = -1;
991
992         /* speed up line processing by only searching to the last '>' */
993         if ((first_pos = strchr(str, '>')) != NULL) {
994                 /* skip a line if it contains a '<' before the initial '>' */
995                 if (memchr(str, '<', first_pos - str) != NULL)
996                         return -1;
997                 last_pos = strrchr(first_pos, '>');
998         } else
999                 return -1;
1000
1001         while (p <= last_pos) {
1002                 while (p < last_pos) {
1003                         if (isspace(*p))
1004                                 p++;
1005                         else
1006                                 break;
1007                 }
1008
1009                 if (*p == '>')
1010                         quote_level++;
1011                 else if (*p != '-' && !isspace(*p) && p <= last_pos) {
1012                         /* any characters are allowed except '-' and space */
1013                         while (*p != '-' && *p != '>' && !isspace(*p) &&
1014                                p < last_pos)
1015                                 p++;
1016                         if (*p == '>')
1017                                 quote_level++;
1018                         else
1019                                 break;
1020                 }
1021
1022                 p++;
1023         }
1024
1025         return quote_level;
1026 }
1027
1028 gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1029 {
1030         register guint haystack_len, needle_len;
1031         gboolean in_squote = FALSE, in_dquote = FALSE;
1032
1033         haystack_len = strlen(haystack);
1034         needle_len   = strlen(needle);
1035
1036         if (haystack_len < needle_len || needle_len == 0)
1037                 return NULL;
1038
1039         while (haystack_len >= needle_len) {
1040                 if (!in_squote && !in_dquote &&
1041                     !strncmp(haystack, needle, needle_len))
1042                         return (gchar *)haystack;
1043
1044                 /* 'foo"bar"' -> foo"bar"
1045                    "foo'bar'" -> foo'bar' */
1046                 if (*haystack == '\'') {
1047                         if (in_squote)
1048                                 in_squote = FALSE;
1049                         else if (!in_dquote)
1050                                 in_squote = TRUE;
1051                 } else if (*haystack == '\"') {
1052                         if (in_dquote)
1053                                 in_dquote = FALSE;
1054                         else if (!in_squote)
1055                                 in_dquote = TRUE;
1056                 }
1057
1058                 haystack++;
1059                 haystack_len--;
1060         }
1061
1062         return NULL;
1063 }
1064
1065 /* this fuction was taken from gstrfuncs.c in glib. */
1066 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1067                             gint max_tokens)
1068 {
1069         GSList *string_list = NULL, *slist;
1070         gchar **str_array, *s, *new_str;
1071         guint i, n = 1, len;
1072
1073         g_return_val_if_fail(str != NULL, NULL);
1074         g_return_val_if_fail(delim != NULL, NULL);
1075
1076         if (max_tokens < 1)
1077                 max_tokens = G_MAXINT;
1078
1079         s = strstr_with_skip_quote(str, delim);
1080         if (s) {
1081                 guint delimiter_len = strlen(delim);
1082
1083                 do {
1084                         len = s - str;
1085                         new_str = g_strndup(str, len);
1086
1087                         if (new_str[0] == '\'' || new_str[0] == '\"') {
1088                                 if (new_str[len - 1] == new_str[0]) {
1089                                         new_str[len - 1] = '\0';
1090                                         memmove(new_str, new_str + 1, len - 1);
1091                                 }
1092                         }
1093                         string_list = g_slist_prepend(string_list, new_str);
1094                         n++;
1095                         str = s + delimiter_len;
1096                         s = strstr_with_skip_quote(str, delim);
1097                 } while (--max_tokens && s);
1098         }
1099
1100         if (*str) {
1101                 new_str = g_strdup(str);
1102                 if (new_str[0] == '\'' || new_str[0] == '\"') {
1103                         len = strlen(str);
1104                         if (new_str[len - 1] == new_str[0]) {
1105                                 new_str[len - 1] = '\0';
1106                                 memmove(new_str, new_str + 1, len - 1);
1107                         }
1108                 }
1109                 string_list = g_slist_prepend(string_list, new_str);
1110                 n++;
1111         }
1112
1113         str_array = g_new(gchar*, n);
1114
1115         i = n - 1;
1116
1117         str_array[i--] = NULL;
1118         for (slist = string_list; slist; slist = slist->next)
1119                 str_array[i--] = slist->data;
1120
1121         g_slist_free(string_list);
1122
1123         return str_array;
1124 }
1125
1126 gchar *get_abbrev_newsgroup_name(const gchar *group)
1127 {
1128         gchar *abbrev_group;
1129         gchar *ap;
1130         const gchar *p = group;
1131
1132         abbrev_group = ap = g_malloc(strlen(group) + 1);
1133
1134         while (*p) {
1135                 while (*p == '.')
1136                         *ap++ = *p++;
1137                 if (strchr(p, '.')) {
1138                         *ap++ = *p++;
1139                         while (*p != '.') p++;
1140                         *ap++ = *p++;
1141                 } else {
1142                         strcpy(ap, p);
1143                         return abbrev_group;
1144                 }
1145         }
1146
1147         *ap = '\0';
1148         return abbrev_group;
1149 }
1150
1151 GList *uri_list_extract_filenames(const gchar *uri_list)
1152 {
1153         GList *result = NULL;
1154         const gchar *p, *q;
1155         gchar *file;
1156
1157         p = uri_list;
1158
1159         while (p) {
1160                 if (*p != '#') {
1161                         while (isspace(*p)) p++;
1162                         if (!strncmp(p, "file:", 5)) {
1163                                 p += 5;
1164                                 q = p;
1165                                 while (*q && *q != '\n' && *q != '\r') q++;
1166
1167                                 if (q > p) {
1168                                         q--;
1169                                         while (q > p && isspace(*q)) q--;
1170                                         file = g_malloc(q - p + 2);
1171                                         strncpy(file, p, q - p + 1);
1172                                         file[q - p + 1] = '\0';
1173                                         result = g_list_append(result,file);
1174                                 }
1175                         }
1176                 }
1177                 p = strchr(p, '\n');
1178                 if (p) p++;
1179         }
1180
1181         return result;
1182 }
1183
1184 #define HEX_TO_INT(val, hex) \
1185 { \
1186         gchar c = hex; \
1187  \
1188         if ('0' <= c && c <= '9') { \
1189                 val = c - '0'; \
1190         } else if ('a' <= c && c <= 'f') { \
1191                 val = c - 'a' + 10; \
1192         } else if ('A' <= c && c <= 'F') { \
1193                 val = c - 'A' + 10; \
1194         } else { \
1195                 val = 0; \
1196         } \
1197 }
1198
1199 /*
1200  * We need this wrapper around g_get_home_dir(), so that
1201  * we can fix some Windoze things here.  Should be done in glibc of course
1202  * but as long as we are not able to do our own extensions to glibc, we do
1203  * it here.
1204  */
1205 gchar *get_home_dir(void)
1206 {
1207 #if HAVE_DOSISH_SYSTEM
1208     static gchar *home_dir;
1209
1210     if (!home_dir) {
1211         home_dir = read_w32_registry_string(NULL,
1212                                             "Software\\Sylpheed", "HomeDir" );
1213         if (!home_dir || !*home_dir) {
1214             if (getenv ("HOMEDRIVE") && getenv("HOMEPATH")) {
1215                 const char *s = g_get_home_dir();
1216                 if (s && *s)
1217                     home_dir = g_strdup (s);
1218             }
1219             if (!home_dir || !*home_dir) 
1220                 home_dir = g_strdup ("c:\\sylpheed");
1221         }
1222         debug_print("initialized home_dir to `%s'\n", home_dir);
1223     }
1224     return home_dir;
1225 #else /* standard glib */
1226     return g_get_home_dir();
1227 #endif
1228 }
1229
1230 gchar *get_rc_dir(void)
1231 {
1232         static gchar *rc_dir = NULL;
1233
1234         if (!rc_dir)
1235                 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1236                                      RC_DIR, NULL);
1237
1238         return rc_dir;
1239 }
1240
1241 gchar *get_news_cache_dir(void)
1242 {
1243         static gchar *news_cache_dir = NULL;
1244
1245         if (!news_cache_dir)
1246                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1247                                              NEWS_CACHE_DIR, NULL);
1248
1249         return news_cache_dir;
1250 }
1251
1252 gchar *get_imap_cache_dir(void)
1253 {
1254         static gchar *imap_cache_dir = NULL;
1255
1256         if (!imap_cache_dir)
1257                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1258                                              IMAP_CACHE_DIR, NULL);
1259
1260         return imap_cache_dir;
1261 }
1262
1263 gchar *get_mbox_cache_dir(void)
1264 {
1265         static gchar *mbox_cache_dir = NULL;
1266
1267         if (!mbox_cache_dir)
1268                 mbox_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1269                                              MBOX_CACHE_DIR, NULL);
1270
1271         return mbox_cache_dir;
1272 }
1273
1274 gchar *get_mime_tmp_dir(void)
1275 {
1276         static gchar *mime_tmp_dir = NULL;
1277
1278         if (!mime_tmp_dir)
1279                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1280                                            MIME_TMP_DIR, NULL);
1281
1282         return mime_tmp_dir;
1283 }
1284
1285 gchar *get_template_dir(void)
1286 {
1287         static gchar *template_dir = NULL;
1288
1289         if (!template_dir)
1290                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1291                                            TEMPLATE_DIR, NULL);
1292
1293         return template_dir;
1294 }
1295
1296 gchar *get_header_cache_dir(void)
1297 {
1298         static gchar *header_dir = NULL;
1299
1300         if (!header_dir)
1301                 header_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1302                                          HEADER_CACHE_DIR, NULL);
1303
1304         return header_dir;
1305 }
1306
1307 gchar *get_tmp_file(void)
1308 {
1309         static gchar *tmp_file = NULL;
1310
1311         if (!tmp_file)
1312                 tmp_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1313                                        "tmpfile", NULL);
1314
1315         return tmp_file;
1316 }
1317
1318 gchar *get_domain_name(void)
1319 {
1320         static gchar *domain_name = NULL;
1321
1322         if (!domain_name) {
1323                 gchar buf[BUFFSIZE] = "";
1324
1325                 if (gethostname(buf, sizeof(buf)) < 0) {
1326                         perror("gethostname");
1327                         strcpy(buf, "unknown");
1328                 }
1329
1330                 domain_name = g_strdup(buf);
1331         }
1332
1333         return domain_name;
1334 }
1335
1336 off_t get_file_size(const gchar *file)
1337 {
1338         struct stat s;
1339
1340         if (stat(file, &s) < 0) {
1341                 FILE_OP_ERROR(file, "stat");
1342                 return -1;
1343         }
1344
1345         return s.st_size;
1346 }
1347
1348 off_t get_file_size_as_crlf(const gchar *file)
1349 {
1350         FILE *fp;
1351         off_t size = 0;
1352         gchar buf[BUFFSIZE];
1353
1354         if ((fp = fopen(file, "rb")) == NULL) {
1355                 FILE_OP_ERROR(file, "fopen");
1356                 return -1;
1357         }
1358
1359         while (fgets(buf, sizeof(buf), fp) != NULL) {
1360                 strretchomp(buf);
1361                 size += strlen(buf) + 2;
1362         }
1363
1364         if (ferror(fp)) {
1365                 FILE_OP_ERROR(file, "fgets");
1366                 size = -1;
1367         }
1368
1369         fclose(fp);
1370
1371         return size;
1372 }
1373
1374 off_t get_left_file_size(FILE *fp)
1375 {
1376         glong pos;
1377         glong end;
1378         off_t size;
1379
1380         if ((pos = ftell(fp)) < 0) {
1381                 perror("ftell");
1382                 return -1;
1383         }
1384         if (fseek(fp, 0L, SEEK_END) < 0) {
1385                 perror("fseek");
1386                 return -1;
1387         }
1388         if ((end = ftell(fp)) < 0) {
1389                 perror("fseek");
1390                 return -1;
1391         }
1392         size = end - pos;
1393         if (fseek(fp, pos, SEEK_SET) < 0) {
1394                 perror("fseek");
1395                 return -1;
1396         }
1397
1398         return size;
1399 }
1400
1401 gboolean file_exist(const gchar *file, gboolean allow_fifo)
1402 {
1403         struct stat s;
1404
1405         if (stat(file, &s) < 0) {
1406                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
1407                 return FALSE;
1408         }
1409
1410         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
1411                 return TRUE;
1412
1413         return FALSE;
1414 }
1415
1416 gboolean is_dir_exist(const gchar *dir)
1417 {
1418         struct stat s;
1419
1420         if (stat(dir, &s) < 0) {
1421                 if (ENOENT != errno) FILE_OP_ERROR(dir, "stat");
1422                 return FALSE;
1423         }
1424
1425         if (S_ISDIR(s.st_mode))
1426                 return TRUE;
1427
1428         return FALSE;
1429 }
1430
1431 gint change_dir(const gchar *dir)
1432 {
1433         gchar *prevdir = NULL;
1434
1435         if (debug_mode)
1436                 prevdir = g_get_current_dir();
1437
1438         if (chdir(dir) < 0) {
1439                 FILE_OP_ERROR(dir, "chdir");
1440                 if (debug_mode) g_free(prevdir);
1441                 return -1;
1442         } else if (debug_mode) {
1443                 gchar *cwd;
1444
1445                 cwd = g_get_current_dir();
1446                 if (strcmp(prevdir, cwd) != 0)
1447                         g_print("current dir: %s\n", cwd);
1448                 g_free(cwd);
1449                 g_free(prevdir);
1450         }
1451
1452         return 0;
1453 }
1454
1455 gint make_dir_hier(const gchar *dir)
1456 {
1457         gchar *parent_dir;
1458         const gchar *p;
1459
1460         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
1461                 parent_dir = g_strndup(dir, p - dir);
1462                 if (*parent_dir != '\0') {
1463                         if (!is_dir_exist(parent_dir)) {
1464                                 if (mkdir(parent_dir, S_IRWXU) < 0) {
1465                                         FILE_OP_ERROR(parent_dir, "mkdir");
1466                                         g_free(parent_dir);
1467                                         return -1;
1468                                 }
1469                                 if (chmod(parent_dir, S_IRWXU) < 0)
1470                                         FILE_OP_ERROR(parent_dir, "chmod");
1471                         }
1472                 }
1473                 g_free(parent_dir);
1474         }
1475         if (!is_dir_exist(dir)) {
1476                 if (mkdir(dir, S_IRWXU) < 0) {
1477                         FILE_OP_ERROR(dir, "mkdir");
1478                         return -1;
1479                 }
1480                 if (chmod(dir, S_IRWXU) < 0)
1481                         FILE_OP_ERROR(dir, "chmod");
1482         }
1483
1484         return 0;
1485 }
1486
1487 gint remove_all_files(const gchar *dir)
1488 {
1489         DIR *dp;
1490         struct dirent *d;
1491         gchar *prev_dir;
1492
1493         prev_dir = g_get_current_dir();
1494
1495         if (chdir(dir) < 0) {
1496                 FILE_OP_ERROR(dir, "chdir");
1497                 return -1;
1498         }
1499
1500         if ((dp = opendir(".")) == NULL) {
1501                 FILE_OP_ERROR(dir, "opendir");
1502                 return -1;
1503         }
1504
1505         while ((d = readdir(dp)) != NULL) {
1506                 if (!strcmp(d->d_name, ".") ||
1507                     !strcmp(d->d_name, ".."))
1508                         continue;
1509
1510                 if (unlink(d->d_name) < 0)
1511                         FILE_OP_ERROR(d->d_name, "unlink");
1512         }
1513
1514         closedir(dp);
1515
1516         if (chdir(prev_dir) < 0) {
1517                 FILE_OP_ERROR(prev_dir, "chdir");
1518                 g_free(prev_dir);
1519                 return -1;
1520         }
1521
1522         g_free(prev_dir);
1523
1524         return 0;
1525 }
1526
1527 gint remove_numbered_files(const gchar *dir, guint first, guint last)
1528 {
1529         DIR *dp;
1530         struct dirent *d;
1531         gchar *prev_dir;
1532         gint fileno;
1533
1534         prev_dir = g_get_current_dir();
1535
1536         if (chdir(dir) < 0) {
1537                 FILE_OP_ERROR(dir, "chdir");
1538                 return -1;
1539         }
1540
1541         if ((dp = opendir(".")) == NULL) {
1542                 FILE_OP_ERROR(dir, "opendir");
1543                 return -1;
1544         }
1545
1546         while ((d = readdir(dp)) != NULL) {
1547                 fileno = to_number(d->d_name);
1548                 if (fileno >= 0 && first <= fileno && fileno <= last) {
1549                         if (unlink(d->d_name) < 0)
1550                                 FILE_OP_ERROR(d->d_name, "unlink");
1551                 }
1552         }
1553
1554         closedir(dp);
1555
1556         if (chdir(prev_dir) < 0) {
1557                 FILE_OP_ERROR(prev_dir, "chdir");
1558                 g_free(prev_dir);
1559                 return -1;
1560         }
1561
1562         g_free(prev_dir);
1563
1564         return 0;
1565 }
1566
1567 gint remove_all_numbered_files(const gchar *dir)
1568 {
1569         return remove_numbered_files(dir, 0, UINT_MAX);
1570 }
1571
1572 gint remove_dir_recursive(const gchar *dir)
1573 {
1574         struct stat s;
1575         DIR *dp;
1576         struct dirent *d;
1577         gchar *prev_dir;
1578
1579         /* g_print("dir = %s\n", dir); */
1580
1581         if (stat(dir, &s) < 0) {
1582                 FILE_OP_ERROR(dir, "stat");
1583                 if (ENOENT == errno) return 0;
1584                 return -1;
1585         }
1586
1587         if (!S_ISDIR(s.st_mode)) {
1588                 if (unlink(dir) < 0) {
1589                         FILE_OP_ERROR(dir, "unlink");
1590                         return -1;
1591                 }
1592
1593                 return 0;
1594         }
1595
1596         prev_dir = g_get_current_dir();
1597         /* g_print("prev_dir = %s\n", prev_dir); */
1598
1599         if (!path_cmp(prev_dir, dir)) {
1600                 g_free(prev_dir);
1601                 if (chdir("..") < 0) {
1602                         FILE_OP_ERROR(dir, "chdir");
1603                         return -1;
1604                 }
1605                 prev_dir = g_get_current_dir();
1606         }
1607
1608         if (chdir(dir) < 0) {
1609                 FILE_OP_ERROR(dir, "chdir");
1610                 g_free(prev_dir);
1611                 return -1;
1612         }
1613
1614         if ((dp = opendir(".")) == NULL) {
1615                 FILE_OP_ERROR(dir, "opendir");
1616                 chdir(prev_dir);
1617                 g_free(prev_dir);
1618                 return -1;
1619         }
1620
1621         /* remove all files in the directory */
1622         while ((d = readdir(dp)) != NULL) {
1623                 if (!strcmp(d->d_name, ".") ||
1624                     !strcmp(d->d_name, ".."))
1625                         continue;
1626
1627                 if (stat(d->d_name, &s) < 0) {
1628                         FILE_OP_ERROR(d->d_name, "stat");
1629                         continue;
1630                 }
1631
1632                 /* g_print("removing %s\n", d->d_name); */
1633
1634                 if (S_ISDIR(s.st_mode)) {
1635                         if (remove_dir_recursive(d->d_name) < 0) {
1636                                 g_warning("can't remove directory\n");
1637                                 return -1;
1638                         }
1639                 } else {
1640                         if (unlink(d->d_name) < 0)
1641                                 FILE_OP_ERROR(d->d_name, "unlink");
1642                 }
1643         }
1644
1645         closedir(dp);
1646
1647         if (chdir(prev_dir) < 0) {
1648                 FILE_OP_ERROR(prev_dir, "chdir");
1649                 g_free(prev_dir);
1650                 return -1;
1651         }
1652
1653         g_free(prev_dir);
1654
1655         if (rmdir(dir) < 0) {
1656                 FILE_OP_ERROR(dir, "rmdir");
1657                 return -1;
1658         }
1659
1660         return 0;
1661 }
1662
1663 #if 0
1664 /* this seems to be slower than the stdio version... */
1665 gint copy_file(const gchar *src, const gchar *dest)
1666 {
1667         gint src_fd, dest_fd;
1668         gint n_read;
1669         gint n_write;
1670         gchar buf[BUFSIZ];
1671         gchar *dest_bak = NULL;
1672
1673         if ((src_fd = open(src, O_RDONLY)) < 0) {
1674                 FILE_OP_ERROR(src, "open");
1675                 return -1;
1676         }
1677
1678         if (is_file_exist(dest)) {
1679                 dest_bak = g_strconcat(dest, ".bak", NULL);
1680                 if (rename(dest, dest_bak) < 0) {
1681                         FILE_OP_ERROR(dest, "rename");
1682                         close(src_fd);
1683                         g_free(dest_bak);
1684                         return -1;
1685                 }
1686         }
1687
1688         if ((dest_fd = open(dest, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
1689                 FILE_OP_ERROR(dest, "open");
1690                 close(src_fd);
1691                 if (dest_bak) {
1692                         if (rename(dest_bak, dest) < 0)
1693                                 FILE_OP_ERROR(dest_bak, "rename");
1694                         g_free(dest_bak);
1695                 }
1696                 return -1;
1697         }
1698
1699         while ((n_read = read(src_fd, buf, sizeof(buf))) > 0) {
1700                 gint len = n_read;
1701                 gchar *bufp = buf;
1702
1703                 while (len > 0) {
1704                         n_write = write(dest_fd, bufp, len);
1705                         if (n_write <= 0) {
1706                                 g_warning(_("writing to %s failed.\n"), dest);
1707                                 close(dest_fd);
1708                                 close(src_fd);
1709                                 unlink(dest);
1710                                 if (dest_bak) {
1711                                         if (rename(dest_bak, dest) < 0)
1712                                                 FILE_OP_ERROR(dest_bak, "rename");
1713                                         g_free(dest_bak);
1714                                 }
1715                                 return -1;
1716                         }
1717                         len -= n_write;
1718                         bufp += n_write;
1719                 }
1720         }
1721
1722         close(src_fd);
1723         close(dest_fd);
1724
1725         if (n_read < 0 || get_file_size(src) != get_file_size(dest)) {
1726                 g_warning(_("File copy from %s to %s failed.\n"), src, dest);
1727                 unlink(dest);
1728                 if (dest_bak) {
1729                         if (rename(dest_bak, dest) < 0)
1730                                 FILE_OP_ERROR(dest_bak, "rename");
1731                         g_free(dest_bak);
1732                 }
1733                 return -1;
1734         }
1735         g_free(dest_bak);
1736
1737         return 0;
1738 }
1739 #endif
1740
1741 gint copy_file(const gchar *src, const gchar *dest)
1742 {
1743         FILE *src_fp, *dest_fp;
1744         gint n_read;
1745         gchar buf[BUFSIZ];
1746         gchar *dest_bak = NULL;
1747         gboolean err = FALSE;
1748
1749         if ((src_fp = fopen(src, "rb")) == NULL) {
1750                 FILE_OP_ERROR(src, "fopen");
1751                 return -1;
1752         }
1753         if (is_file_exist(dest)) {
1754                 dest_bak = g_strconcat(dest, ".bak", NULL);
1755                 if (rename(dest, dest_bak) < 0) {
1756                         FILE_OP_ERROR(dest, "rename");
1757                         fclose(src_fp);
1758                         g_free(dest_bak);
1759                         return -1;
1760                 }
1761         }
1762
1763         if ((dest_fp = fopen(dest, "wb")) == NULL) {
1764                 FILE_OP_ERROR(dest, "fopen");
1765                 fclose(src_fp);
1766                 if (dest_bak) {
1767                         if (rename(dest_bak, dest) < 0)
1768                                 FILE_OP_ERROR(dest_bak, "rename");
1769                         g_free(dest_bak);
1770                 }
1771                 return -1;
1772         }
1773
1774         if (change_file_mode_rw(dest_fp, dest) < 0) {
1775                 FILE_OP_ERROR(dest, "chmod");
1776                 g_warning(_("can't change file mode\n"));
1777         }
1778
1779         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
1780                 if (n_read < sizeof(buf) && ferror(src_fp))
1781                         break;
1782                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
1783                         g_warning(_("writing to %s failed.\n"), dest);
1784                         fclose(dest_fp);
1785                         fclose(src_fp);
1786                         unlink(dest);
1787                         if (dest_bak) {
1788                                 if (rename(dest_bak, dest) < 0)
1789                                         FILE_OP_ERROR(dest_bak, "rename");
1790                                 g_free(dest_bak);
1791                         }
1792                         return -1;
1793                 }
1794         }
1795
1796         if (ferror(src_fp)) {
1797                 FILE_OP_ERROR(src, "fread");
1798                 err = TRUE;
1799         }
1800         fclose(src_fp);
1801         if (fclose(dest_fp) == EOF) {
1802                 FILE_OP_ERROR(dest, "fclose");
1803                 err = TRUE;
1804         }
1805
1806         if (err) {
1807                 unlink(dest);
1808                 if (dest_bak) {
1809                         if (rename(dest_bak, dest) < 0)
1810                                 FILE_OP_ERROR(dest_bak, "rename");
1811                         g_free(dest_bak);
1812                 }
1813                 return -1;
1814         }
1815
1816         g_free(dest_bak);
1817
1818         return 0;
1819 }
1820
1821 gint move_file(const gchar *src, const gchar *dest)
1822 {
1823         if (is_file_exist(dest)) {
1824                 g_warning(_("move_file(): file %s already exists."), dest);
1825                 return -1;
1826         }
1827
1828         if (rename(src, dest) == 0) return 0;
1829
1830         if (EXDEV != errno) {
1831                 FILE_OP_ERROR(src, "rename");
1832                 return -1;
1833         }
1834
1835         if (copy_file(src, dest) < 0) return -1;
1836
1837         unlink(src);
1838
1839         return 0;
1840 }
1841
1842 gint change_file_mode_rw(FILE *fp, const gchar *file)
1843 {
1844 #if HAVE_FCHMOD
1845         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
1846 #else
1847         return chmod(file, S_IRUSR|S_IWUSR);
1848 #endif
1849 }
1850
1851 FILE *my_tmpfile(void)
1852 {
1853 #if HAVE_MKSTEMP
1854         const gchar suffix[] = ".XXXXXX";
1855         const gchar *tmpdir;
1856         guint tmplen;
1857         const gchar *progname;
1858         guint proglen;
1859         gchar *fname;
1860         gint fd;
1861         FILE *fp;
1862
1863         tmpdir = g_get_tmp_dir();
1864         tmplen = strlen(tmpdir);
1865         progname = g_get_prgname();
1866         proglen = strlen(progname);
1867         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
1868                 return tmpfile());
1869
1870         memcpy(fname, tmpdir, tmplen);
1871         fname[tmplen] = G_DIR_SEPARATOR;
1872         memcpy(fname + tmplen + 1, progname, proglen);
1873         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
1874
1875         fd = mkstemp(fname);
1876         if (fd < 0)
1877                 return tmpfile();
1878
1879         unlink(fname);
1880
1881         fp = fdopen(fd, "w+b");
1882         if (!fp)
1883                 close(fd);
1884         else
1885                 return fp;
1886 #endif /* HAVE_MKSTEMP */
1887
1888         return tmpfile();
1889 }
1890
1891 FILE *str_open_as_stream(const gchar *str)
1892 {
1893         FILE *fp;
1894         size_t len;
1895
1896         g_return_val_if_fail(str != NULL, NULL);
1897
1898         fp = my_tmpfile();
1899         if (!fp) {
1900                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
1901                 return NULL;
1902         }
1903
1904         len = strlen(str);
1905         if (len == 0) return fp;
1906
1907         if (fwrite(str, len, 1, fp) != 1) {
1908                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
1909                 fclose(fp);
1910                 return NULL;
1911         }
1912
1913         rewind(fp);
1914         return fp;
1915 }
1916
1917 gint execute_async(gchar *const argv[])
1918 {
1919         pid_t pid;
1920
1921         if ((pid = fork()) < 0) {
1922                 perror("fork");
1923                 return -1;
1924         }
1925
1926         if (pid == 0) {                 /* child process */
1927                 pid_t gch_pid;
1928
1929                 if ((gch_pid = fork()) < 0) {
1930                         perror("fork");
1931                         _exit(1);
1932                 }
1933
1934                 if (gch_pid == 0) {     /* grandchild process */
1935                         execvp(argv[0], argv);
1936
1937                         perror("execvp");
1938                         _exit(1);
1939                 }
1940
1941                 _exit(0);
1942         }
1943
1944         waitpid(pid, NULL, 0);
1945
1946         return 0;
1947 }
1948
1949 gint execute_sync(gchar *const argv[])
1950 {
1951         pid_t pid;
1952
1953         if ((pid = fork()) < 0) {
1954                 perror("fork");
1955                 return -1;
1956         }
1957
1958         if (pid == 0) {         /* child process */
1959                 execvp(argv[0], argv);
1960
1961                 perror("execvp");
1962                 _exit(1);
1963         }
1964
1965         waitpid(pid, NULL, 0);
1966
1967         return 0;
1968 }
1969
1970 gint execute_command_line(const gchar *cmdline, gboolean async)
1971 {
1972         gchar **argv;
1973         gint ret;
1974
1975         argv = strsplit_with_quote(cmdline, " ", 0);
1976
1977         if (async)
1978                 ret = execute_async(argv);
1979         else
1980                 ret = execute_sync(argv);
1981         g_strfreev(argv);
1982
1983         return ret;
1984 }
1985
1986 static gint is_unchanged_uri_char(char c)
1987 {
1988         switch (c) {
1989                 case '(':
1990                 case ')':
1991                 case ',':
1992                         return 0;
1993                 default:
1994                         return 1;
1995         }
1996 }
1997
1998 void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
1999 {
2000         int i;
2001         int k;
2002
2003         k = 0;
2004         for(i = 0; i < strlen(uri) ; i++) {
2005                 if (is_unchanged_uri_char(uri[i])) {
2006                         if (k + 2 >= bufsize)
2007                                 break;
2008                         encoded_uri[k++] = uri[i];
2009                 }
2010                 else {
2011                         char * hexa = "0123456789ABCDEF";
2012                         
2013                         if (k + 4 >= bufsize)
2014                                 break;
2015                         encoded_uri[k++] = '%';
2016                         encoded_uri[k++] = hexa[uri[i] / 16];
2017                         encoded_uri[k++] = hexa[uri[i] % 16];
2018                 }
2019         }
2020         encoded_uri[k] = 0;
2021 }
2022
2023 /* Converts two-digit hexadecimal to decimal.  Used for unescaping escaped 
2024  * characters
2025  */
2026 static gint axtoi(const gchar *hexstr)
2027 {
2028         gint hi, lo, result;
2029        
2030         hi = hexstr[0];
2031         if ('0' <= hi && hi <= '9') {
2032                 hi -= '0';
2033         } else
2034                 if ('a' <= hi && hi <= 'f') {
2035                         hi -= ('a' - 10);
2036                 } else
2037                         if ('A' <= hi && hi <= 'F') {
2038                                 hi -= ('A' - 10);
2039                         }
2040
2041         lo = hexstr[1];
2042         if ('0' <= lo && lo <= '9') {
2043                 lo -= '0';
2044         } else
2045                 if ('a' <= lo && lo <= 'f') {
2046                         lo -= ('a'-10);
2047                 } else
2048                         if ('A' <= lo && lo <= 'F') {
2049                                 lo -= ('A' - 10);
2050                         }
2051         result = lo + (16 * hi);
2052         return result;
2053 }
2054
2055
2056 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
2057  * plusses, and escape characters are used)
2058  */
2059
2060 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
2061 {
2062         const gchar *encoded;
2063         gchar *decoded;
2064
2065         encoded = encoded_uri;
2066         decoded = decoded_uri;
2067
2068         while (*encoded) {
2069                 if (*encoded == '%') {
2070                         encoded++;
2071                         if (isxdigit(encoded[0])
2072                             && isxdigit(encoded[1])) {
2073                                 *decoded = (gchar) axtoi(encoded);
2074                                 decoded++;
2075                                 encoded += 2;
2076                         }
2077                 }
2078                 else if (*encoded == '+') {
2079                         *decoded = ' ';
2080                         decoded++;
2081                         encoded++;
2082                 }
2083                 else {
2084                         *decoded = *encoded;
2085                         decoded++;
2086                         encoded++;
2087                 }
2088         }
2089
2090         *decoded = '\0';
2091 }
2092
2093
2094 gint open_uri(const gchar *uri, const gchar *cmdline)
2095 {
2096         static gchar *default_cmdline = "netscape -remote openURL(%s,raise)";
2097         gchar buf[BUFFSIZE];
2098         gchar *p;
2099         gchar encoded_uri[BUFFSIZE];
2100         
2101         g_return_val_if_fail(uri != NULL, -1);
2102
2103         /* an option to choose whether to use encode_uri or not ? */
2104         encode_uri(encoded_uri, BUFFSIZE, uri);
2105         
2106         if (cmdline &&
2107             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
2108             !strchr(p + 2, '%'))
2109                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
2110         else {
2111                 if (cmdline)
2112                         g_warning(_("Open URI command line is invalid: `%s'"),
2113                                   cmdline);
2114                 g_snprintf(buf, sizeof(buf), default_cmdline, encoded_uri);
2115         }
2116         
2117         execute_command_line(buf, TRUE);
2118
2119         return 0;
2120 }
2121
2122 time_t remote_tzoffset_sec(const gchar *zone)
2123 {
2124         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
2125         gchar zone3[4];
2126         gchar *p;
2127         gchar c;
2128         gint iustz;
2129         gint offset;
2130         time_t remoteoffset;
2131
2132         strncpy(zone3, zone, 3);
2133         zone3[3] = '\0';
2134         remoteoffset = 0;
2135
2136         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
2137             (c == '+' || c == '-')) {
2138                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
2139                 if (c == '-')
2140                         remoteoffset = -remoteoffset;
2141         } else if (!strncmp(zone, "UT" , 2) ||
2142                    !strncmp(zone, "GMT", 2)) {
2143                 remoteoffset = 0;
2144         } else if (strlen(zone3) == 3 &&
2145                    (p = strstr(ustzstr, zone3)) != NULL &&
2146                    (p - ustzstr) % 3 == 0) {
2147                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
2148                 remoteoffset = iustz * 3600;
2149         } else if (strlen(zone3) == 1) {
2150                 switch (zone[0]) {
2151                 case 'Z': remoteoffset =   0; break;
2152                 case 'A': remoteoffset =  -1; break;
2153                 case 'B': remoteoffset =  -2; break;
2154                 case 'C': remoteoffset =  -3; break;
2155                 case 'D': remoteoffset =  -4; break;
2156                 case 'E': remoteoffset =  -5; break;
2157                 case 'F': remoteoffset =  -6; break;
2158                 case 'G': remoteoffset =  -7; break;
2159                 case 'H': remoteoffset =  -8; break;
2160                 case 'I': remoteoffset =  -9; break;
2161                 case 'K': remoteoffset = -10; break; /* J is not used */
2162                 case 'L': remoteoffset = -11; break;
2163                 case 'M': remoteoffset = -12; break;
2164                 case 'N': remoteoffset =   1; break;
2165                 case 'O': remoteoffset =   2; break;
2166                 case 'P': remoteoffset =   3; break;
2167                 case 'Q': remoteoffset =   4; break;
2168                 case 'R': remoteoffset =   5; break;
2169                 case 'S': remoteoffset =   6; break;
2170                 case 'T': remoteoffset =   7; break;
2171                 case 'U': remoteoffset =   8; break;
2172                 case 'V': remoteoffset =   9; break;
2173                 case 'W': remoteoffset =  10; break;
2174                 case 'X': remoteoffset =  11; break;
2175                 case 'Y': remoteoffset =  12; break;
2176                 default:  remoteoffset =   0; break;
2177                 }
2178                 remoteoffset = remoteoffset * 3600;
2179         }
2180
2181         return remoteoffset;
2182 }
2183
2184 time_t tzoffset_sec(time_t *now)
2185 {
2186         struct tm gmt, *lt;
2187         gint off;
2188
2189         gmt = *gmtime(now);
2190         lt = localtime(now);
2191
2192         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
2193
2194         if (lt->tm_year < gmt.tm_year)
2195                 off -= 24 * 60;
2196         else if (lt->tm_year > gmt.tm_year)
2197                 off += 24 * 60;
2198         else if (lt->tm_yday < gmt.tm_yday)
2199                 off -= 24 * 60;
2200         else if (lt->tm_yday > gmt.tm_yday)
2201                 off += 24 * 60;
2202
2203         if (off >= 24 * 60)             /* should be impossible */
2204                 off = 23 * 60 + 59;     /* if not, insert silly value */
2205         if (off <= -24 * 60)
2206                 off = -(23 * 60 + 59);
2207         if (off > 12 * 60)
2208                 off -= 24 * 60;
2209         if (off < -12 * 60)
2210                 off += 24 * 60;
2211
2212         return off * 60;
2213 }
2214
2215 /* calculate timezone offset */
2216 gchar *tzoffset(time_t *now)
2217 {
2218         static gchar offset_string[6];
2219         struct tm gmt, *lt;
2220         gint off;
2221         gchar sign = '+';
2222
2223         gmt = *gmtime(now);
2224         lt = localtime(now);
2225
2226         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
2227
2228         if (lt->tm_year < gmt.tm_year)
2229                 off -= 24 * 60;
2230         else if (lt->tm_year > gmt.tm_year)
2231                 off += 24 * 60;
2232         else if (lt->tm_yday < gmt.tm_yday)
2233                 off -= 24 * 60;
2234         else if (lt->tm_yday > gmt.tm_yday)
2235                 off += 24 * 60;
2236
2237         if (off < 0) {
2238                 sign = '-';
2239                 off = -off;
2240         }
2241
2242         if (off >= 24 * 60)             /* should be impossible */
2243                 off = 23 * 60 + 59;     /* if not, insert silly value */
2244
2245         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
2246
2247         return offset_string;
2248 }
2249
2250 void get_rfc822_date(gchar *buf, gint len)
2251 {
2252         struct tm *lt;
2253         time_t t;
2254         gchar day[4], mon[4];
2255         gint dd, hh, mm, ss, yyyy;
2256
2257         t = time(NULL);
2258         lt = localtime(&t);
2259
2260         sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n",
2261                day, mon, &dd, &hh, &mm, &ss, &yyyy);
2262         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
2263                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
2264 }
2265
2266 static FILE *log_fp = NULL;
2267
2268 void set_log_file(const gchar *filename)
2269 {
2270         if (log_fp) return;
2271         log_fp = fopen(filename, "wb");
2272         if (!log_fp)
2273                 FILE_OP_ERROR(filename, "fopen");
2274 }
2275
2276 void close_log_file(void)
2277 {
2278         if (log_fp) {
2279                 fclose(log_fp);
2280                 log_fp = NULL;
2281         }
2282 }
2283
2284 static guint log_verbosity_count = 0;
2285
2286 void log_verbosity_set(gboolean verbose)
2287 {
2288         if (verbose)
2289                 log_verbosity_count++;
2290         else if (log_verbosity_count > 0)
2291                 log_verbosity_count--;
2292 }
2293
2294 void debug_print_real(const gchar *format, ...)
2295 {
2296         va_list args;
2297         gchar buf[BUFFSIZE];
2298
2299         if (!debug_mode) return;
2300
2301         va_start(args, format);
2302         g_vsnprintf(buf, sizeof(buf), format, args);
2303         va_end(args);
2304
2305         fputs(buf, stdout);
2306 }
2307
2308 void log_print(const gchar *format, ...)
2309 {
2310         va_list args;
2311         gchar buf[BUFFSIZE];
2312         gchar *logbuf;
2313         gchar timestr[6];
2314         time_t t;
2315
2316         va_start(args, format);
2317         g_vsnprintf(buf, sizeof(buf), format, args);
2318         va_end(args);
2319         
2320         time(&t);
2321         strftime(timestr, 6, "%H:%M", localtime(&t));
2322         logbuf = g_strdup_printf("[%s] %s", timestr, buf);
2323
2324         if (debug_mode) fputs(logbuf, stdout);
2325         log_window_append(logbuf, LOG_NORMAL);
2326         if (log_fp) {
2327                 fputs(logbuf, log_fp);
2328                 fflush(log_fp);
2329         }
2330         if (log_verbosity_count)
2331                 statusbar_puts_all(buf);
2332         g_free(logbuf);
2333 }
2334
2335 void log_message(const gchar *format, ...)
2336 {
2337         va_list args;
2338         gchar buf[BUFFSIZE];
2339
2340         va_start(args, format);
2341         g_vsnprintf(buf, sizeof(buf), format, args);
2342         va_end(args);
2343
2344         if (debug_mode) g_message("%s", buf);
2345         log_window_append(buf, LOG_MSG);
2346         if (log_fp) {
2347                 fputs("message: ", log_fp);
2348                 fputs(buf, log_fp);
2349                 fflush(log_fp);
2350         }
2351         statusbar_puts_all(buf);
2352 }
2353
2354 void log_warning(const gchar *format, ...)
2355 {
2356         va_list args;
2357         gchar buf[BUFFSIZE];
2358
2359         va_start(args, format);
2360         g_vsnprintf(buf, sizeof(buf), format, args);
2361         va_end(args);
2362
2363         g_warning("%s", buf);
2364         log_window_append(buf, LOG_WARN);
2365         if (log_fp) {
2366                 fputs("*** warning: ", log_fp);
2367                 fputs(buf, log_fp);
2368                 fflush(log_fp);
2369         }
2370 }
2371
2372 void log_error(const gchar *format, ...)
2373 {
2374         va_list args;
2375         gchar buf[BUFFSIZE];
2376
2377         va_start(args, format);
2378         g_vsnprintf(buf, sizeof(buf), format, args);
2379         va_end(args);
2380
2381         g_warning("%s", buf);
2382         log_window_append(buf, LOG_ERROR);
2383         if (log_fp) {
2384                 fputs("*** error: ", log_fp);
2385                 fputs(buf, log_fp);
2386                 fflush(log_fp);
2387         }
2388 }
2389
2390
2391 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
2392 {
2393         if (subject == NULL)
2394                 subject = "";
2395
2396         if (g_strncasecmp(subject, "Re: ", 4) == 0)
2397                 return g_hash_table_lookup(subject_table, subject + 4);
2398         else
2399                 return g_hash_table_lookup(subject_table, subject);
2400 }
2401
2402 void subject_table_insert(GHashTable *subject_table, gchar * subject,
2403                           void * data)
2404 {
2405         if (subject == NULL)
2406                 return;
2407         if (* subject == 0)
2408                 return;
2409         if (g_strcasecmp(subject, "Re:") == 0)
2410                 return;
2411         if (g_strcasecmp(subject, "Re: ") == 0)
2412                 return;
2413
2414         if (g_strncasecmp(subject, "Re: ", 4) == 0)
2415                 g_hash_table_insert(subject_table, subject + 4, data);
2416         else
2417                 g_hash_table_insert(subject_table, subject, data);
2418 }
2419
2420 void subject_table_remove(GHashTable *subject_table, gchar * subject)
2421 {
2422         if (subject == NULL)
2423                 return;
2424
2425         if (g_strncasecmp(subject, "Re: ", 4) == 0)
2426                 g_hash_table_remove(subject_table, subject + 4);
2427         else
2428                 g_hash_table_remove(subject_table, subject);
2429 }
2430
2431 gboolean subject_is_reply(const gchar *subject)
2432 {
2433         /* XXX: just simply here so someone can handle really
2434          * advanced Re: detection like "Re[4]", "ANTW:" or
2435          * Re: Re: Re: Re: Re: Re: Re: Re:" stuff. */
2436         if (subject == NULL) return FALSE;
2437         else return 0 == g_strncasecmp(subject, "Re: ", 4);
2438 }