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